prettier-plugin-intlayer 0.0.0-stage → 9.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +46 -2
  2. package/dist/cjs/@intlayer/core/dist/esm/interpreter/getEnumeration.cjs +42 -0
  3. package/dist/cjs/@intlayer/core/dist/esm/interpreter/getEnumeration.cjs.map +1 -0
  4. package/dist/cjs/collapseTranslations.cjs +73 -0
  5. package/dist/cjs/collapseTranslations.cjs.map +1 -0
  6. package/dist/cjs/index.cjs +84 -0
  7. package/dist/cjs/index.cjs.map +1 -0
  8. package/dist/cjs/keyOrderings.cjs +135 -0
  9. package/dist/cjs/keyOrderings.cjs.map +1 -0
  10. package/dist/cjs/sortContentKeys.cjs +86 -0
  11. package/dist/cjs/sortContentKeys.cjs.map +1 -0
  12. package/dist/cjs/sortContentNodes.cjs +131 -0
  13. package/dist/cjs/sortContentNodes.cjs.map +1 -0
  14. package/dist/esm/@intlayer/core/dist/esm/interpreter/getEnumeration.mjs +42 -0
  15. package/dist/esm/@intlayer/core/dist/esm/interpreter/getEnumeration.mjs.map +1 -0
  16. package/dist/esm/collapseTranslations.mjs +72 -0
  17. package/dist/esm/collapseTranslations.mjs.map +1 -0
  18. package/dist/esm/index.mjs +70 -0
  19. package/dist/esm/index.mjs.map +1 -0
  20. package/dist/esm/keyOrderings.mjs +128 -0
  21. package/dist/esm/keyOrderings.mjs.map +1 -0
  22. package/dist/esm/sortContentKeys.mjs +84 -0
  23. package/dist/esm/sortContentKeys.mjs.map +1 -0
  24. package/dist/esm/sortContentNodes.mjs +123 -0
  25. package/dist/esm/sortContentNodes.mjs.map +1 -0
  26. package/dist/types/collapseTranslations.d.ts +8 -0
  27. package/dist/types/collapseTranslations.d.ts.map +1 -0
  28. package/dist/types/index.d.ts +12 -0
  29. package/dist/types/index.d.ts.map +1 -0
  30. package/dist/types/keyOrderings.d.ts +36 -0
  31. package/dist/types/keyOrderings.d.ts.map +1 -0
  32. package/dist/types/sortContentKeys.d.ts +8 -0
  33. package/dist/types/sortContentKeys.d.ts.map +1 -0
  34. package/dist/types/sortContentNodes.d.ts +46 -0
  35. package/dist/types/sortContentNodes.d.ts.map +1 -0
  36. package/package.json +94 -4
package/README.md CHANGED
@@ -1,3 +1,47 @@
1
- # Temporary Holding Version
1
+ # prettier-plugin-intlayer
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Prettier plugin for [Intlayer](https://intlayer.org). It puts the keys of your
4
+ translations in a fixed order every time Prettier formats a file.
5
+
6
+ | Helper | Order |
7
+ | ---------- | --------------------------------------------------------------- |
8
+ | `t()` | pinned locales (`en`), then alphabetical |
9
+ | `plural()` | CLDR: `zero`, `one`, `two`, `few`, `many`, `other` |
10
+ | `enu()` | ascending threshold, `fallback` last (only if behavior-safe) |
11
+ | `select()` | alphabetical, `fallback` last (implicit last-key fallback kept) |
12
+
13
+ The serialized `.content.json` nodes (`{ "nodeType": "translation", … }`) are
14
+ sorted the same way.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install --save-dev prettier prettier-plugin-intlayer
20
+ ```
21
+
22
+ Or let `intlayer init prettier` install and configure it.
23
+
24
+ ## Usage
25
+
26
+ ```json
27
+ {
28
+ "plugins": ["prettier-plugin-intlayer"],
29
+ "intlayerPinnedLocales": ["en"],
30
+ "intlayerSortContentKeys": false
31
+ }
32
+ ```
33
+
34
+ | Option | Default | Description |
35
+ | --------------------------------------- | -------- | ---------------------------------------------------------------------------------- |
36
+ | `intlayerPinnedLocales` | `["en"]` | Locales kept first in `t()`, in this order. `[]` sorts them all. |
37
+ | `intlayerSortContentKeys` | `false` | In `*.content.*` files, also sort the dictionary content keys and nested objects. |
38
+ | `intlayerCollapseIdenticalTranslations` | `true` | In `*.content.*` files, `t({ en: 'Next.js', fr: 'Next.js' })` becomes `'Next.js'`. |
39
+
40
+ Objects holding a spread, a computed key, a method or a duplicated key are
41
+ left untouched. Comments move with their key.
42
+
43
+ Prettier runs one plugin per parser: if another plugin also replaces the JS/TS
44
+ parsers (e.g. `prettier-plugin-tailwindcss`), scope this one to `*.content.*`
45
+ files with an override.
46
+
47
+ Documentation: https://intlayer.org/doc/prettier
@@ -0,0 +1,42 @@
1
+ // ../@intlayer/core/dist/esm/interpreter/getEnumeration.mjs
2
+ /**
3
+ * Find the matching condition for a quantity.
4
+ *
5
+ * Usage:
6
+ *
7
+ * ```ts
8
+ * const key = findMatchingCondition({
9
+ * '<=-2.3': 'You have less than -2.3',
10
+ * '<1': 'You have less than one',
11
+ * '2': 'You have two',
12
+ * '>=3': 'You have three or more',
13
+ * }, 2);
14
+ * // '2'
15
+ * ```
16
+ *
17
+ * The order of the keys will define the priority of the content.
18
+ *
19
+ * ```ts
20
+ * const key = findMatchingCondition({
21
+ * '<4': 'You have less than four',
22
+ * '2': 'You have two',
23
+ * }, 2);
24
+ * // '<4'
25
+ * ```
26
+ *
27
+ * If no keys match, the default key is '1'.
28
+ */
29
+ const findMatchingCondition = (enumerationContent, quantity) => {
30
+ const numericKeys = Object.keys(enumerationContent);
31
+ for (const key of numericKeys) {
32
+ const isEqual = !key.startsWith(">") && !key.startsWith("<") && !key.startsWith("=") && parseFloat(key) === quantity || key.startsWith("=") && parseFloat(key.slice(1)) === quantity;
33
+ const isSuperior = key.startsWith(">") && quantity > parseFloat(key.slice(1));
34
+ const isSuperiorOrEqual = key.startsWith(">=") && quantity >= parseFloat(key.slice(2));
35
+ const isInferior = key.startsWith("<") && quantity < parseFloat(key.slice(1));
36
+ const isInferiorOrEqual = key.startsWith("<=") && quantity <= parseFloat(key.slice(2));
37
+ if (isEqual || isSuperior || isSuperiorOrEqual || isInferior || isInferiorOrEqual) return key;
38
+ }
39
+ };
40
+
41
+ exports.findMatchingCondition = findMatchingCondition;
42
+ //# sourceMappingURL=getEnumeration.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getEnumeration.cjs","names":[],"sources":["../../../../../../../../@intlayer/core/dist/esm/interpreter/getEnumeration.mjs"],"sourcesContent":["// src/interpreter/getEnumeration.ts\n/**\n* Find the matching condition for a quantity.\n*\n* Usage:\n*\n* ```ts\n* const key = findMatchingCondition({\n* '<=-2.3': 'You have less than -2.3',\n* '<1': 'You have less than one',\n* '2': 'You have two',\n* '>=3': 'You have three or more',\n* }, 2);\n* // '2'\n* ```\n*\n* The order of the keys will define the priority of the content.\n*\n* ```ts\n* const key = findMatchingCondition({\n* '<4': 'You have less than four',\n* '2': 'You have two',\n* }, 2);\n* // '<4'\n* ```\n*\n* If no keys match, the default key is '1'.\n*/\nconst findMatchingCondition = (enumerationContent, quantity) => {\n\tconst numericKeys = Object.keys(enumerationContent);\n\tfor (const key of numericKeys) {\n\t\tconst isEqual = !key.startsWith(\">\") && !key.startsWith(\"<\") && !key.startsWith(\"=\") && parseFloat(key) === quantity || key.startsWith(\"=\") && parseFloat(key.slice(1)) === quantity;\n\t\tconst isSuperior = key.startsWith(\">\") && quantity > parseFloat(key.slice(1));\n\t\tconst isSuperiorOrEqual = key.startsWith(\">=\") && quantity >= parseFloat(key.slice(2));\n\t\tconst isInferior = key.startsWith(\"<\") && quantity < parseFloat(key.slice(1));\n\t\tconst isInferiorOrEqual = key.startsWith(\"<=\") && quantity <= parseFloat(key.slice(2));\n\t\tif (isEqual || isSuperior || isSuperiorOrEqual || isInferior || isInferiorOrEqual) return key;\n\t}\n};\n/**\n* Picks content from an enumeration map based on a provided quantity.\n*\n* Supported keys in the enumeration map:\n* - Specific numbers: '0', '1', '2'\n* - Comparison operators: '<5', '>=10', '<=2'\n* - Fallback: 'fallback'\n*\n* The first matching key in the object's iteration order will be selected.\n*\n* @param enumerationContent - A map of conditions/quantities to content.\n* @param quantity - The number to match against the conditions.\n* @returns The matching content.\n*\n* @example\n* ```ts\n* const content = getEnumeration({\n* '0': 'No items',\n* '1': 'One item',\n* '>1': 'Many items',\n* }, 5);\n* // 'Many items'\n* ```\n*/\nconst getEnumeration = (enumerationContent, quantity) => {\n\treturn enumerationContent[findMatchingCondition(enumerationContent, quantity) ?? \"fallback\"];\n};\n\nexport { findMatchingCondition, getEnumeration };\n//# sourceMappingURL=getEnumeration.mjs.map"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAM,yBAAyB,oBAAoB,aAAa;CAC/D,MAAM,cAAc,OAAO,KAAK,kBAAkB;CAClD,KAAK,MAAM,OAAO,aAAa;EAC9B,MAAM,UAAU,CAAC,IAAI,WAAW,GAAG,KAAK,CAAC,IAAI,WAAW,GAAG,KAAK,CAAC,IAAI,WAAW,GAAG,KAAK,WAAW,GAAG,MAAM,YAAY,IAAI,WAAW,GAAG,KAAK,WAAW,IAAI,MAAM,CAAC,CAAC,MAAM;EAC5K,MAAM,aAAa,IAAI,WAAW,GAAG,KAAK,WAAW,WAAW,IAAI,MAAM,CAAC,CAAC;EAC5E,MAAM,oBAAoB,IAAI,WAAW,IAAI,KAAK,YAAY,WAAW,IAAI,MAAM,CAAC,CAAC;EACrF,MAAM,aAAa,IAAI,WAAW,GAAG,KAAK,WAAW,WAAW,IAAI,MAAM,CAAC,CAAC;EAC5E,MAAM,oBAAoB,IAAI,WAAW,IAAI,KAAK,YAAY,WAAW,IAAI,MAAM,CAAC,CAAC;EACrF,IAAI,WAAW,cAAc,qBAAqB,cAAc,mBAAmB,OAAO;CAC3F;AACD"}
@@ -0,0 +1,73 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_sortContentNodes = require("./sortContentNodes.cjs");
3
+
4
+ // src/collapseTranslations.ts
5
+ /** Fields of a serialized translation node that may be collapsed. */
6
+ const SERIALIZED_TRANSLATION_FIELDS = /* @__PURE__ */ new Set(["nodeType", "translation"]);
7
+ /** Start / end offsets, from Babel (`start`/`end`) or ESTree (`range`). */
8
+ const getNodeBounds = (node) => {
9
+ if (Array.isArray(node.range)) return node.range;
10
+ if (typeof node.start === "number" && typeof node.end === "number") return [node.start, node.end];
11
+ };
12
+ const isStringLiteral = (node) => require_sortContentNodes.isAstNode(node) && (node.type === "StringLiteral" || node.type === "Literal") && typeof node.value === "string";
13
+ /**
14
+ * The string literal every locale shares, or `undefined` when the locales
15
+ * differ, hold anything but a plain string, or there is only one locale.
16
+ */
17
+ const getSharedStringLiteral = (translationMap) => {
18
+ const properties = translationMap.properties;
19
+ if (properties.length < 2 || !properties.every(require_sortContentNodes.isPlainProperty)) return;
20
+ const values = properties.map((property) => property.value);
21
+ if (!values.every(isStringLiteral)) return void 0;
22
+ const [firstValue] = values;
23
+ return values.every((value) => value.value === firstValue.value) ? firstValue : void 0;
24
+ };
25
+ /** Replaces a node in place by another one, keeping the object identity. */
26
+ const replaceNodeInPlace = (target, replacement) => {
27
+ for (const field of Object.keys(target)) delete target[field];
28
+ Object.assign(target, replacement);
29
+ };
30
+ /**
31
+ * The node to collapse and the literal replacing it: a `t({...})` call, or a
32
+ * serialized `{ nodeType: 'translation', translation: {...} }` object.
33
+ */
34
+ const getCollapsibleTranslation = (node) => {
35
+ const helperContent = require_sortContentNodes.getHelperCallContent(node);
36
+ if (helperContent) return helperContent.nodeType === "translation" && node.arguments.length === 1 && !node.typeArguments && !node.typeParameters ? getSharedStringLiteral(helperContent.content) : void 0;
37
+ const serializedContent = require_sortContentNodes.getSerializedNodeContent(node);
38
+ if (serializedContent?.nodeType !== "translation") return void 0;
39
+ return node.properties.every((property) => require_sortContentNodes.isPlainProperty(property) && SERIALIZED_TRANSLATION_FIELDS.has(require_sortContentNodes.getStaticKeyName(property) ?? "")) ? getSharedStringLiteral(serializedContent.content) : void 0;
40
+ };
41
+ /**
42
+ * Replaces, in place, every translation whose locales all hold the same
43
+ * string by that string: `t({ en: 'Next.js', fr: 'Next.js' })` → `'Next.js'`.
44
+ *
45
+ * Translations holding a comment are kept, so no comment is lost.
46
+ */
47
+ const collapseIdenticalTranslationsInAst = (rootNode) => {
48
+ const commentBounds = (require_sortContentNodes.isAstNode(rootNode) && Array.isArray(rootNode.comments) ? rootNode.comments : []).map(getNodeBounds).filter((bounds) => bounds !== void 0);
49
+ const containsComment = (node) => {
50
+ const bounds = getNodeBounds(node);
51
+ if (!bounds) return true;
52
+ return commentBounds.some(([commentStart, commentEnd]) => commentStart >= bounds[0] && commentEnd <= bounds[1]);
53
+ };
54
+ const visitedNodes = /* @__PURE__ */ new WeakSet();
55
+ const pendingValues = [rootNode];
56
+ while (pendingValues.length > 0) {
57
+ const value = pendingValues.pop();
58
+ if (typeof value !== "object" || value === null) continue;
59
+ if (visitedNodes.has(value)) continue;
60
+ visitedNodes.add(value);
61
+ if (require_sortContentNodes.isAstNode(value)) {
62
+ const sharedLiteral = getCollapsibleTranslation(value);
63
+ if (sharedLiteral && !containsComment(value)) {
64
+ replaceNodeInPlace(value, { ...sharedLiteral });
65
+ continue;
66
+ }
67
+ }
68
+ for (const [field, child] of Object.entries(value)) if (field !== "loc" && field !== "range" && field !== "parent") pendingValues.push(child);
69
+ }
70
+ };
71
+
72
+ exports.collapseIdenticalTranslationsInAst = collapseIdenticalTranslationsInAst;
73
+ //# sourceMappingURL=collapseTranslations.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collapseTranslations.cjs","names":["isAstNode","isPlainProperty","getHelperCallContent","getSerializedNodeContent","getStaticKeyName"],"sources":["../../src/collapseTranslations.ts"],"sourcesContent":["import {\n type AstNode,\n getHelperCallContent,\n getSerializedNodeContent,\n getStaticKeyName,\n isAstNode,\n isPlainProperty,\n} from './sortContentNodes';\n\n/** Fields of a serialized translation node that may be collapsed. */\nconst SERIALIZED_TRANSLATION_FIELDS = new Set(['nodeType', 'translation']);\n\n/** Start / end offsets, from Babel (`start`/`end`) or ESTree (`range`). */\nconst getNodeBounds = (node: AstNode): [number, number] | undefined => {\n if (Array.isArray(node.range)) return node.range as [number, number];\n if (typeof node.start === 'number' && typeof node.end === 'number') {\n return [node.start, node.end];\n }\n\n return undefined;\n};\n\nconst isStringLiteral = (node: unknown): node is AstNode =>\n isAstNode(node) &&\n (node.type === 'StringLiteral' || node.type === 'Literal') &&\n typeof node.value === 'string';\n\n/**\n * The string literal every locale shares, or `undefined` when the locales\n * differ, hold anything but a plain string, or there is only one locale.\n */\nconst getSharedStringLiteral = (\n translationMap: AstNode\n): AstNode | undefined => {\n const properties = translationMap.properties as unknown[];\n\n if (properties.length < 2 || !properties.every(isPlainProperty)) {\n return undefined;\n }\n\n const values = (properties as AstNode[]).map((property) => property.value);\n\n if (!values.every(isStringLiteral)) return undefined;\n\n const [firstValue] = values;\n\n return values.every((value) => value.value === firstValue.value)\n ? firstValue\n : undefined;\n};\n\n/** Replaces a node in place by another one, keeping the object identity. */\nconst replaceNodeInPlace = (target: AstNode, replacement: AstNode): void => {\n for (const field of Object.keys(target)) {\n delete target[field];\n }\n\n Object.assign(target, replacement);\n};\n\n/**\n * The node to collapse and the literal replacing it: a `t({...})` call, or a\n * serialized `{ nodeType: 'translation', translation: {...} }` object.\n */\nconst getCollapsibleTranslation = (node: AstNode): AstNode | undefined => {\n const helperContent = getHelperCallContent(node);\n\n if (helperContent) {\n const isPlainCall =\n helperContent.nodeType === 'translation' &&\n (node.arguments as unknown[]).length === 1 &&\n !node.typeArguments &&\n !node.typeParameters;\n\n return isPlainCall\n ? getSharedStringLiteral(helperContent.content)\n : undefined;\n }\n\n const serializedContent = getSerializedNodeContent(node);\n\n if (serializedContent?.nodeType !== 'translation') return undefined;\n\n const hasOnlyTranslationFields = (node.properties as unknown[]).every(\n (property) =>\n isPlainProperty(property) &&\n SERIALIZED_TRANSLATION_FIELDS.has(getStaticKeyName(property) ?? '')\n );\n\n return hasOnlyTranslationFields\n ? getSharedStringLiteral(serializedContent.content)\n : undefined;\n};\n\n/**\n * Replaces, in place, every translation whose locales all hold the same\n * string by that string: `t({ en: 'Next.js', fr: 'Next.js' })` → `'Next.js'`.\n *\n * Translations holding a comment are kept, so no comment is lost.\n */\nexport const collapseIdenticalTranslationsInAst = (rootNode: unknown): void => {\n const commentBounds = (\n isAstNode(rootNode) && Array.isArray(rootNode.comments)\n ? (rootNode.comments as AstNode[])\n : []\n )\n .map(getNodeBounds)\n .filter((bounds): bounds is [number, number] => bounds !== undefined);\n\n const containsComment = (node: AstNode): boolean => {\n const bounds = getNodeBounds(node);\n\n if (!bounds) return true;\n\n return commentBounds.some(\n ([commentStart, commentEnd]) =>\n commentStart >= bounds[0] && commentEnd <= bounds[1]\n );\n };\n\n const visitedNodes = new WeakSet<object>();\n const pendingValues: unknown[] = [rootNode];\n\n while (pendingValues.length > 0) {\n const value = pendingValues.pop();\n\n if (typeof value !== 'object' || value === null) continue;\n if (visitedNodes.has(value)) continue;\n\n visitedNodes.add(value);\n\n if (isAstNode(value)) {\n const sharedLiteral = getCollapsibleTranslation(value);\n\n if (sharedLiteral && !containsComment(value)) {\n replaceNodeInPlace(value, { ...sharedLiteral });\n continue;\n }\n }\n\n for (const [field, child] of Object.entries(value)) {\n if (field !== 'loc' && field !== 'range' && field !== 'parent') {\n pendingValues.push(child);\n }\n }\n }\n};\n"],"mappings":";;;;;AAUA,MAAM,gDAAgC,IAAI,IAAI,CAAC,YAAY,aAAa,CAAC;;AAGzE,MAAM,iBAAiB,SAAgD;CACrE,IAAI,MAAM,QAAQ,KAAK,KAAK,GAAG,OAAO,KAAK;CAC3C,IAAI,OAAO,KAAK,UAAU,YAAY,OAAO,KAAK,QAAQ,UACxD,OAAO,CAAC,KAAK,OAAO,KAAK,GAAG;AAIhC;AAEA,MAAM,mBAAmB,SACvBA,mCAAU,IAAI,MACb,KAAK,SAAS,mBAAmB,KAAK,SAAS,cAChD,OAAO,KAAK,UAAU;;;;;AAMxB,MAAM,0BACJ,mBACwB;CACxB,MAAM,aAAa,eAAe;CAElC,IAAI,WAAW,SAAS,KAAK,CAAC,WAAW,MAAMC,wCAAe,GAC5D;CAGF,MAAM,SAAU,WAAyB,KAAK,aAAa,SAAS,KAAK;CAEzE,IAAI,CAAC,OAAO,MAAM,eAAe,GAAG,OAAO;CAE3C,MAAM,CAAC,cAAc;CAErB,OAAO,OAAO,OAAO,UAAU,MAAM,UAAU,WAAW,KAAK,IAC3D,aACA;AACN;;AAGA,MAAM,sBAAsB,QAAiB,gBAA+B;CAC1E,KAAK,MAAM,SAAS,OAAO,KAAK,MAAM,GACpC,OAAO,OAAO;CAGhB,OAAO,OAAO,QAAQ,WAAW;AACnC;;;;;AAMA,MAAM,6BAA6B,SAAuC;CACxE,MAAM,gBAAgBC,8CAAqB,IAAI;CAE/C,IAAI,eAOF,OALE,cAAc,aAAa,iBAC1B,KAAK,UAAwB,WAAW,KACzC,CAAC,KAAK,iBACN,CAAC,KAAK,iBAGJ,uBAAuB,cAAc,OAAO,IAC5C;CAGN,MAAM,oBAAoBC,kDAAyB,IAAI;CAEvD,IAAI,mBAAmB,aAAa,eAAe,OAAO;CAQ1D,OANkC,KAAK,WAAyB,OAC7D,aACCF,yCAAgB,QAAQ,KACxB,8BAA8B,IAAIG,0CAAiB,QAAQ,KAAK,EAAE,CAGxC,IAC1B,uBAAuB,kBAAkB,OAAO,IAChD;AACN;;;;;;;AAQA,MAAa,sCAAsC,aAA4B;CAC7E,MAAM,iBACJJ,mCAAU,QAAQ,KAAK,MAAM,QAAQ,SAAS,QAAQ,IACjD,SAAS,WACV,CAAC,EAAC,CAEL,IAAI,aAAa,CAAC,CAClB,QAAQ,WAAuC,WAAW,MAAS;CAEtE,MAAM,mBAAmB,SAA2B;EAClD,MAAM,SAAS,cAAc,IAAI;EAEjC,IAAI,CAAC,QAAQ,OAAO;EAEpB,OAAO,cAAc,MAClB,CAAC,cAAc,gBACd,gBAAgB,OAAO,MAAM,cAAc,OAAO,EACtD;CACF;CAEA,MAAM,+BAAe,IAAI,QAAgB;CACzC,MAAM,gBAA2B,CAAC,QAAQ;CAE1C,OAAO,cAAc,SAAS,GAAG;EAC/B,MAAM,QAAQ,cAAc,IAAI;EAEhC,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EACjD,IAAI,aAAa,IAAI,KAAK,GAAG;EAE7B,aAAa,IAAI,KAAK;EAEtB,IAAIA,mCAAU,KAAK,GAAG;GACpB,MAAM,gBAAgB,0BAA0B,KAAK;GAErD,IAAI,iBAAiB,CAAC,gBAAgB,KAAK,GAAG;IAC5C,mBAAmB,OAAO,EAAE,GAAG,cAAc,CAAC;IAC9C;GACF;EACF;EAEA,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,KAAK,GAC/C,IAAI,UAAU,SAAS,UAAU,WAAW,UAAU,UACpD,cAAc,KAAK,KAAK;CAG9B;AACF"}
@@ -0,0 +1,84 @@
1
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
+ const require_collapseTranslations = require("./collapseTranslations.cjs");
3
+ const require_sortContentKeys = require("./sortContentKeys.cjs");
4
+ const require_sortContentNodes = require("./sortContentNodes.cjs");
5
+ const require_keyOrderings = require("./keyOrderings.cjs");
6
+ let prettier_plugins_acorn = require("prettier/plugins/acorn");
7
+ let prettier_plugins_babel = require("prettier/plugins/babel");
8
+ let prettier_plugins_flow = require("prettier/plugins/flow");
9
+ let prettier_plugins_meriyah = require("prettier/plugins/meriyah");
10
+ let prettier_plugins_typescript = require("prettier/plugins/typescript");
11
+
12
+ // src/index.ts
13
+ /** Locales kept at the top of every translation when none are configured. */
14
+ const DEFAULT_PINNED_LOCALES = ["en"];
15
+ const readPinnedLocales = (options) => options.intlayerPinnedLocales ?? DEFAULT_PINNED_LOCALES;
16
+ /** Wraps a built-in parser so the parsed AST gets its content nodes sorted. */
17
+ const withSortedTranslations = (parser) => ({
18
+ ...parser,
19
+ parse: async (text, options) => {
20
+ const ast = await parser.parse(text, options);
21
+ const pluginOptions = options;
22
+ const isContentDeclaration = require_sortContentKeys.isContentFile(options.filepath);
23
+ if (pluginOptions.intlayerCollapseIdenticalTranslations !== false && isContentDeclaration) require_collapseTranslations.collapseIdenticalTranslationsInAst(ast);
24
+ require_sortContentNodes.sortContentNodesInAst(ast, readPinnedLocales(options));
25
+ if (pluginOptions.intlayerSortContentKeys && isContentDeclaration) require_sortContentKeys.sortContentKeysInAst(ast);
26
+ return ast;
27
+ }
28
+ });
29
+ const wrapParsers = (sourceParsers, parserNames) => Object.fromEntries(parserNames.map((parserName) => [parserName, withSortedTranslations(sourceParsers[parserName])]));
30
+ const parsers = {
31
+ ...wrapParsers(prettier_plugins_babel.parsers, [
32
+ "babel",
33
+ "babel-flow",
34
+ "babel-ts",
35
+ "json",
36
+ "json5",
37
+ "jsonc"
38
+ ]),
39
+ ...wrapParsers(prettier_plugins_typescript.parsers, ["typescript"]),
40
+ ...wrapParsers(prettier_plugins_acorn.parsers, ["acorn", "espree"]),
41
+ ...wrapParsers(prettier_plugins_flow.parsers, ["flow"]),
42
+ ...wrapParsers(prettier_plugins_meriyah.parsers, ["meriyah"])
43
+ };
44
+ const options = {
45
+ intlayerPinnedLocales: {
46
+ type: "string",
47
+ array: true,
48
+ category: "Intlayer",
49
+ default: [{ value: [...DEFAULT_PINNED_LOCALES] }],
50
+ description: "Locales kept first, in this order, in every t() translation. The other locales are sorted alphabetically."
51
+ },
52
+ intlayerCollapseIdenticalTranslations: {
53
+ type: "boolean",
54
+ category: "Intlayer",
55
+ default: true,
56
+ description: "In .content files, replace a t() whose locales all hold the same string by that string: t({ en: 'Next.js', fr: 'Next.js' }) becomes 'Next.js'."
57
+ },
58
+ intlayerSortContentKeys: {
59
+ type: "boolean",
60
+ category: "Intlayer",
61
+ default: false,
62
+ description: "In .content files, also sort the keys of the dictionary content and of its nested objects alphabetically."
63
+ }
64
+ };
65
+ const plugin = {
66
+ parsers,
67
+ options
68
+ };
69
+
70
+ exports.DEFAULT_PINNED_LOCALES = DEFAULT_PINNED_LOCALES;
71
+ exports.PLURAL_CATEGORY_ORDER = require_keyOrderings.PLURAL_CATEGORY_ORDER;
72
+ exports.collapseIdenticalTranslationsInAst = require_collapseTranslations.collapseIdenticalTranslationsInAst;
73
+ exports.createLocaleComparator = require_keyOrderings.createLocaleComparator;
74
+ exports.default = plugin;
75
+ exports.isContentFile = require_sortContentKeys.isContentFile;
76
+ exports.options = options;
77
+ exports.orderAlphabetically = require_keyOrderings.orderAlphabetically;
78
+ exports.orderEnumerationConditions = require_keyOrderings.orderEnumerationConditions;
79
+ exports.orderPluralCategories = require_keyOrderings.orderPluralCategories;
80
+ exports.orderSelectCases = require_keyOrderings.orderSelectCases;
81
+ exports.parsers = parsers;
82
+ exports.sortContentKeysInAst = require_sortContentKeys.sortContentKeysInAst;
83
+ exports.sortContentNodesInAst = require_sortContentNodes.sortContentNodesInAst;
84
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["isContentFile","babelParsers","typescriptParsers","acornParsers","flowParsers","meriyahParsers"],"sources":["../../src/index.ts"],"sourcesContent":["import type { Parser, ParserOptions, Plugin, SupportOptions } from 'prettier';\nimport { parsers as acornParsers } from 'prettier/plugins/acorn';\nimport { parsers as babelParsers } from 'prettier/plugins/babel';\nimport { parsers as flowParsers } from 'prettier/plugins/flow';\nimport { parsers as meriyahParsers } from 'prettier/plugins/meriyah';\nimport { parsers as typescriptParsers } from 'prettier/plugins/typescript';\nimport { collapseIdenticalTranslationsInAst } from './collapseTranslations';\nimport { isContentFile, sortContentKeysInAst } from './sortContentKeys';\nimport { sortContentNodesInAst } from './sortContentNodes';\n\nexport { collapseIdenticalTranslationsInAst } from './collapseTranslations';\nexport {\n createLocaleComparator,\n orderAlphabetically,\n orderEnumerationConditions,\n orderPluralCategories,\n orderSelectCases,\n PLURAL_CATEGORY_ORDER,\n} from './keyOrderings';\nexport { isContentFile, sortContentKeysInAst } from './sortContentKeys';\nexport { sortContentNodesInAst } from './sortContentNodes';\n\n/** Locales kept at the top of every translation when none are configured. */\nexport const DEFAULT_PINNED_LOCALES = ['en'] as const;\n\ntype IntlayerPluginOptions = ParserOptions & {\n intlayerPinnedLocales?: string[];\n intlayerSortContentKeys?: boolean;\n intlayerCollapseIdenticalTranslations?: boolean;\n};\n\nconst readPinnedLocales = (options: ParserOptions): readonly string[] =>\n (options as IntlayerPluginOptions).intlayerPinnedLocales ??\n DEFAULT_PINNED_LOCALES;\n\n/** Wraps a built-in parser so the parsed AST gets its content nodes sorted. */\nconst withSortedTranslations = <AstType>(\n parser: Parser<AstType>\n): Parser<AstType> => ({\n ...parser,\n parse: async (text, options) => {\n const ast = await parser.parse(text, options);\n const pluginOptions = options as IntlayerPluginOptions;\n const isContentDeclaration = isContentFile(options.filepath);\n\n if (\n pluginOptions.intlayerCollapseIdenticalTranslations !== false &&\n isContentDeclaration\n ) {\n collapseIdenticalTranslationsInAst(ast);\n }\n\n sortContentNodesInAst(ast, readPinnedLocales(options));\n\n if (pluginOptions.intlayerSortContentKeys && isContentDeclaration) {\n sortContentKeysInAst(ast);\n }\n\n return ast;\n },\n});\n\nconst wrapParsers = (\n sourceParsers: Record<string, Parser>,\n parserNames: readonly string[]\n): Record<string, Parser> =>\n Object.fromEntries(\n parserNames.map((parserName) => [\n parserName,\n withSortedTranslations(sourceParsers[parserName]),\n ])\n );\n\nexport const parsers: Plugin['parsers'] = {\n ...wrapParsers(babelParsers, [\n 'babel',\n 'babel-flow',\n 'babel-ts',\n 'json',\n 'json5',\n 'jsonc',\n ]),\n ...wrapParsers(typescriptParsers, ['typescript']),\n ...wrapParsers(acornParsers, ['acorn', 'espree']),\n ...wrapParsers(flowParsers, ['flow']),\n ...wrapParsers(meriyahParsers, ['meriyah']),\n};\n\nexport const options: SupportOptions = {\n intlayerPinnedLocales: {\n type: 'string',\n array: true,\n category: 'Intlayer',\n default: [{ value: [...DEFAULT_PINNED_LOCALES] }],\n description:\n 'Locales kept first, in this order, in every t() translation. The other locales are sorted alphabetically.',\n },\n intlayerCollapseIdenticalTranslations: {\n type: 'boolean',\n category: 'Intlayer',\n default: true,\n description:\n \"In .content files, replace a t() whose locales all hold the same string by that string: t({ en: 'Next.js', fr: 'Next.js' }) becomes 'Next.js'.\",\n },\n intlayerSortContentKeys: {\n type: 'boolean',\n category: 'Intlayer',\n default: false,\n description:\n 'In .content files, also sort the keys of the dictionary content and of its nested objects alphabetically.',\n },\n};\n\nconst plugin: Plugin = { parsers, options };\n\nexport default plugin;\n"],"mappings":";;;;;;;;;;;;;AAuBA,MAAa,yBAAyB,CAAC,IAAI;AAQ3C,MAAM,qBAAqB,YACxB,QAAkC,yBACnC;;AAGF,MAAM,0BACJ,YACqB;CACrB,GAAG;CACH,OAAO,OAAO,MAAM,YAAY;EAC9B,MAAM,MAAM,MAAM,OAAO,MAAM,MAAM,OAAO;EAC5C,MAAM,gBAAgB;EACtB,MAAM,uBAAuBA,sCAAc,QAAQ,QAAQ;EAE3D,IACE,cAAc,0CAA0C,SACxD,sBAEA,gEAAmC,GAAG;EAGxC,+CAAsB,KAAK,kBAAkB,OAAO,CAAC;EAErD,IAAI,cAAc,2BAA2B,sBAC3C,6CAAqB,GAAG;EAG1B,OAAO;CACT;AACF;AAEA,MAAM,eACJ,eACA,gBAEA,OAAO,YACL,YAAY,KAAK,eAAe,CAC9B,YACA,uBAAuB,cAAc,WAAW,CAClD,CAAC,CACH;AAEF,MAAa,UAA6B;CACxC,GAAG,YAAYC,gCAAc;EAC3B;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,GAAG,YAAYC,qCAAmB,CAAC,YAAY,CAAC;CAChD,GAAG,YAAYC,gCAAc,CAAC,SAAS,QAAQ,CAAC;CAChD,GAAG,YAAYC,+BAAa,CAAC,MAAM,CAAC;CACpC,GAAG,YAAYC,kCAAgB,CAAC,SAAS,CAAC;AAC5C;AAEA,MAAa,UAA0B;CACrC,uBAAuB;EACrB,MAAM;EACN,OAAO;EACP,UAAU;EACV,SAAS,CAAC,EAAE,OAAO,CAAC,GAAG,sBAAsB,EAAE,CAAC;EAChD,aACE;CACJ;CACA,uCAAuC;EACrC,MAAM;EACN,UAAU;EACV,SAAS;EACT,aACE;CACJ;CACA,yBAAyB;EACvB,MAAM;EACN,UAAU;EACV,SAAS;EACT,aACE;CACJ;AACF;AAEA,MAAM,SAAiB;CAAE;CAAS;AAAQ"}
@@ -0,0 +1,135 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_getEnumeration = require("./@intlayer/core/dist/esm/interpreter/getEnumeration.cjs");
3
+
4
+ // src/keyOrderings.ts
5
+ const compareStrings = (stringA, stringB) => {
6
+ if (stringA === stringB) return 0;
7
+ return stringA < stringB ? -1 : 1;
8
+ };
9
+ /** Plain alphabetical order, for the keys of a content object. */
10
+ const orderAlphabetically = (keys) => [...keys].sort(compareStrings);
11
+ /**
12
+ * Builds the comparator ordering locales: pinned locales first (in the given
13
+ * order), then every other locale alphabetically.
14
+ */
15
+ const createLocaleComparator = (pinnedLocales) => {
16
+ const pinnedRank = new Map(pinnedLocales.map((locale, index) => [locale, index]));
17
+ return (localeA, localeB) => {
18
+ const rankA = pinnedRank.get(localeA) ?? Number.POSITIVE_INFINITY;
19
+ const rankB = pinnedRank.get(localeB) ?? Number.POSITIVE_INFINITY;
20
+ if (rankA !== rankB) return rankA < rankB ? -1 : 1;
21
+ return compareStrings(localeA, localeB);
22
+ };
23
+ };
24
+ /** Locale order for `t()`: pinned locales, then alphabetical. */
25
+ const createLocaleOrdering = (pinnedLocales) => {
26
+ const compareLocales = createLocaleComparator(pinnedLocales);
27
+ return (keys) => [...keys].sort(compareLocales);
28
+ };
29
+ /** CLDR plural categories, from the smallest quantity to the fallback. */
30
+ const PLURAL_CATEGORY_ORDER = [
31
+ "zero",
32
+ "one",
33
+ "two",
34
+ "few",
35
+ "many",
36
+ "other"
37
+ ];
38
+ /**
39
+ * CLDR order for `plural()`. Unknown keys leave the object untouched.
40
+ */
41
+ const orderPluralCategories = (keys) => {
42
+ const categoryRank = new Map(PLURAL_CATEGORY_ORDER.map((category, index) => [category, index]));
43
+ if (!keys.every((key) => categoryRank.has(key))) return void 0;
44
+ return [...keys].sort((keyA, keyB) => categoryRank.get(keyA) - categoryRank.get(keyB));
45
+ };
46
+ const FALLBACK_KEY = "fallback";
47
+ const ENUMERATION_CONDITION_PATTERN = /^(<=|>=|<|>|=)?(-?\d+(?:\.\d+)?)$/;
48
+ /** Rank of each operator for one threshold: `<1`, `<=1`, `1`, `>=1`, `>1`. */
49
+ const OPERATOR_RANK = {
50
+ "<": 0,
51
+ "<=": 1,
52
+ "": 2,
53
+ "=": 2,
54
+ ">=": 3,
55
+ ">": 4
56
+ };
57
+ const parseEnumerationCondition = (key) => {
58
+ const match = ENUMERATION_CONDITION_PATTERN.exec(key);
59
+ if (!match) return void 0;
60
+ return {
61
+ key,
62
+ threshold: Number(match[2]),
63
+ operatorRank: OPERATOR_RANK[match[1] ?? ""]
64
+ };
65
+ };
66
+ /**
67
+ * Keys in the order the runtime iterates them: JavaScript lists integer-like
68
+ * keys first, ascending, whatever their position in the source.
69
+ */
70
+ const toRuntimeKeyOrder = (keys) => Object.keys(Object.fromEntries(keys.map((key) => [key, true])));
71
+ /**
72
+ * Quantities covering every interval the conditions can distinguish: each
73
+ * threshold, the midpoint between two thresholds, and one value beyond each
74
+ * end.
75
+ */
76
+ const getProbeQuantities = (conditions) => {
77
+ const thresholds = [...new Set(conditions.map((condition) => condition.threshold))].sort((thresholdA, thresholdB) => thresholdA - thresholdB);
78
+ if (thresholds.length === 0) return [];
79
+ const midpoints = thresholds.slice(1).map((threshold, index) => (thresholds[index] + threshold) / 2);
80
+ return [
81
+ thresholds[0] - 1,
82
+ ...thresholds,
83
+ ...midpoints,
84
+ thresholds[thresholds.length - 1] + 1
85
+ ];
86
+ };
87
+ const resolvesIdentically = (originalKeys, reorderedKeys, quantities) => {
88
+ const toContentState = (keys) => Object.fromEntries(toRuntimeKeyOrder(keys).map((key) => [key, key]));
89
+ const originalContent = toContentState(originalKeys);
90
+ const reorderedContent = toContentState(reorderedKeys);
91
+ return quantities.every((quantity) => require_getEnumeration.findMatchingCondition(originalContent, quantity) === require_getEnumeration.findMatchingCondition(reorderedContent, quantity));
92
+ };
93
+ /**
94
+ * Ascending threshold order for `enu()`, `fallback` last.
95
+ *
96
+ * The first matching condition wins at runtime, so the new order is applied
97
+ * only when it selects the same branch for every quantity; overlapping
98
+ * conditions whose priority depends on their position are left untouched.
99
+ */
100
+ const orderEnumerationConditions = (keys) => {
101
+ const conditionKeys = keys.filter((key) => key !== FALLBACK_KEY);
102
+ const conditions = conditionKeys.map(parseEnumerationCondition);
103
+ if (conditions.some((condition) => condition === void 0)) return;
104
+ const sortedConditionKeys = conditions.sort((conditionA, conditionB) => conditionA.threshold - conditionB.threshold || conditionA.operatorRank - conditionB.operatorRank || compareStrings(conditionA.key, conditionB.key)).map((condition) => condition.key);
105
+ if (!resolvesIdentically(conditionKeys, sortedConditionKeys, getProbeQuantities(conditions))) return;
106
+ const fallbackKeys = keys.filter((key) => key === FALLBACK_KEY);
107
+ return [...sortedConditionKeys, ...fallbackKeys];
108
+ };
109
+ /** Keys `getSelect` falls back to, by name, before the last declared key. */
110
+ const SELECT_NAMED_FALLBACK_KEYS = ["fallback", "other"];
111
+ /**
112
+ * Alphabetical order for `select()`, `fallback` last.
113
+ *
114
+ * Without a `fallback` or `other` key, the last key is the implicit fallback,
115
+ * so it stays last and only the keys before it are sorted.
116
+ */
117
+ const orderSelectCases = (keys) => {
118
+ const hasNamedFallback = keys.some((key) => SELECT_NAMED_FALLBACK_KEYS.includes(key));
119
+ const runtimeKeys = toRuntimeKeyOrder(keys);
120
+ const pinnedLastKey = hasNamedFallback ? keys.includes(FALLBACK_KEY) ? FALLBACK_KEY : void 0 : runtimeKeys[runtimeKeys.length - 1];
121
+ const sortedKeys = keys.filter((key) => key !== pinnedLastKey).sort(compareStrings);
122
+ const orderedKeys = pinnedLastKey === void 0 ? sortedKeys : [...sortedKeys, pinnedLastKey];
123
+ const reorderedRuntimeKeys = toRuntimeKeyOrder(orderedKeys);
124
+ if (!hasNamedFallback && reorderedRuntimeKeys[reorderedRuntimeKeys.length - 1] !== pinnedLastKey) return;
125
+ return orderedKeys;
126
+ };
127
+
128
+ exports.PLURAL_CATEGORY_ORDER = PLURAL_CATEGORY_ORDER;
129
+ exports.createLocaleComparator = createLocaleComparator;
130
+ exports.createLocaleOrdering = createLocaleOrdering;
131
+ exports.orderAlphabetically = orderAlphabetically;
132
+ exports.orderEnumerationConditions = orderEnumerationConditions;
133
+ exports.orderPluralCategories = orderPluralCategories;
134
+ exports.orderSelectCases = orderSelectCases;
135
+ //# sourceMappingURL=keyOrderings.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyOrderings.cjs","names":["findMatchingCondition"],"sources":["../../src/keyOrderings.ts"],"sourcesContent":["import { findMatchingCondition } from '@intlayer/core/interpreter';\nimport type { EnumerationContentState } from '@intlayer/core/transpiler';\n\n/**\n * Returns the given keys in their canonical order, or `undefined` when the\n * keys must keep their current order.\n */\nexport type KeyOrdering = (keys: readonly string[]) => string[] | undefined;\n\nconst compareStrings = (stringA: string, stringB: string): number => {\n if (stringA === stringB) return 0;\n\n return stringA < stringB ? -1 : 1;\n};\n\n/** Plain alphabetical order, for the keys of a content object. */\nexport const orderAlphabetically: KeyOrdering = (keys) =>\n [...keys].sort(compareStrings);\n\n/**\n * Builds the comparator ordering locales: pinned locales first (in the given\n * order), then every other locale alphabetically.\n */\nexport const createLocaleComparator = (pinnedLocales: readonly string[]) => {\n const pinnedRank = new Map(\n pinnedLocales.map((locale, index) => [locale, index])\n );\n\n return (localeA: string, localeB: string): number => {\n const rankA = pinnedRank.get(localeA) ?? Number.POSITIVE_INFINITY;\n const rankB = pinnedRank.get(localeB) ?? Number.POSITIVE_INFINITY;\n\n if (rankA !== rankB) return rankA < rankB ? -1 : 1;\n\n return compareStrings(localeA, localeB);\n };\n};\n\n/** Locale order for `t()`: pinned locales, then alphabetical. */\nexport const createLocaleOrdering = (\n pinnedLocales: readonly string[]\n): KeyOrdering => {\n const compareLocales = createLocaleComparator(pinnedLocales);\n\n return (keys) => [...keys].sort(compareLocales);\n};\n\n/** CLDR plural categories, from the smallest quantity to the fallback. */\nexport const PLURAL_CATEGORY_ORDER = [\n 'zero',\n 'one',\n 'two',\n 'few',\n 'many',\n 'other',\n] as const;\n\n/**\n * CLDR order for `plural()`. Unknown keys leave the object untouched.\n */\nexport const orderPluralCategories: KeyOrdering = (keys) => {\n const categoryRank = new Map<string, number>(\n PLURAL_CATEGORY_ORDER.map((category, index) => [category, index])\n );\n\n if (!keys.every((key) => categoryRank.has(key))) return undefined;\n\n return [...keys].sort(\n (keyA, keyB) =>\n (categoryRank.get(keyA) as number) - (categoryRank.get(keyB) as number)\n );\n};\n\nconst FALLBACK_KEY = 'fallback';\n\nconst ENUMERATION_CONDITION_PATTERN = /^(<=|>=|<|>|=)?(-?\\d+(?:\\.\\d+)?)$/;\n\n/** Rank of each operator for one threshold: `<1`, `<=1`, `1`, `>=1`, `>1`. */\nconst OPERATOR_RANK: Record<string, number> = {\n '<': 0,\n '<=': 1,\n '': 2,\n '=': 2,\n '>=': 3,\n '>': 4,\n};\n\ntype EnumerationCondition = {\n key: string;\n threshold: number;\n operatorRank: number;\n};\n\nconst parseEnumerationCondition = (\n key: string\n): EnumerationCondition | undefined => {\n const match = ENUMERATION_CONDITION_PATTERN.exec(key);\n\n if (!match) return undefined;\n\n return {\n key,\n threshold: Number(match[2]),\n operatorRank: OPERATOR_RANK[match[1] ?? ''],\n };\n};\n\n/**\n * Keys in the order the runtime iterates them: JavaScript lists integer-like\n * keys first, ascending, whatever their position in the source.\n */\nconst toRuntimeKeyOrder = (keys: readonly string[]): string[] =>\n Object.keys(Object.fromEntries(keys.map((key) => [key, true])));\n\n/**\n * Quantities covering every interval the conditions can distinguish: each\n * threshold, the midpoint between two thresholds, and one value beyond each\n * end.\n */\nconst getProbeQuantities = (\n conditions: readonly EnumerationCondition[]\n): number[] => {\n const thresholds = [\n ...new Set(conditions.map((condition) => condition.threshold)),\n ].sort((thresholdA, thresholdB) => thresholdA - thresholdB);\n\n if (thresholds.length === 0) return [];\n\n const midpoints = thresholds\n .slice(1)\n .map((threshold, index) => (thresholds[index] + threshold) / 2);\n\n return [\n thresholds[0] - 1,\n ...thresholds,\n ...midpoints,\n thresholds[thresholds.length - 1] + 1,\n ];\n};\n\nconst resolvesIdentically = (\n originalKeys: readonly string[],\n reorderedKeys: readonly string[],\n quantities: readonly number[]\n): boolean => {\n const toContentState = (keys: readonly string[]) =>\n Object.fromEntries(\n toRuntimeKeyOrder(keys).map((key) => [key, key])\n ) as EnumerationContentState<string>;\n const originalContent = toContentState(originalKeys);\n const reorderedContent = toContentState(reorderedKeys);\n\n return quantities.every(\n (quantity) =>\n findMatchingCondition(originalContent, quantity) ===\n findMatchingCondition(reorderedContent, quantity)\n );\n};\n\n/**\n * Ascending threshold order for `enu()`, `fallback` last.\n *\n * The first matching condition wins at runtime, so the new order is applied\n * only when it selects the same branch for every quantity; overlapping\n * conditions whose priority depends on their position are left untouched.\n */\nexport const orderEnumerationConditions: KeyOrdering = (keys) => {\n const conditionKeys = keys.filter((key) => key !== FALLBACK_KEY);\n const conditions = conditionKeys.map(parseEnumerationCondition);\n\n if (conditions.some((condition) => condition === undefined)) {\n return undefined;\n }\n\n const sortedConditionKeys = (conditions as EnumerationCondition[])\n .sort(\n (conditionA, conditionB) =>\n conditionA.threshold - conditionB.threshold ||\n conditionA.operatorRank - conditionB.operatorRank ||\n compareStrings(conditionA.key, conditionB.key)\n )\n .map((condition) => condition.key);\n\n if (\n !resolvesIdentically(\n conditionKeys,\n sortedConditionKeys,\n getProbeQuantities(conditions as EnumerationCondition[])\n )\n ) {\n return undefined;\n }\n\n const fallbackKeys = keys.filter((key) => key === FALLBACK_KEY);\n\n return [...sortedConditionKeys, ...fallbackKeys];\n};\n\n/** Keys `getSelect` falls back to, by name, before the last declared key. */\nconst SELECT_NAMED_FALLBACK_KEYS = ['fallback', 'other'];\n\n/**\n * Alphabetical order for `select()`, `fallback` last.\n *\n * Without a `fallback` or `other` key, the last key is the implicit fallback,\n * so it stays last and only the keys before it are sorted.\n */\nexport const orderSelectCases: KeyOrdering = (keys) => {\n const hasNamedFallback = keys.some((key) =>\n SELECT_NAMED_FALLBACK_KEYS.includes(key)\n );\n const runtimeKeys = toRuntimeKeyOrder(keys);\n const pinnedLastKey = hasNamedFallback\n ? keys.includes(FALLBACK_KEY)\n ? FALLBACK_KEY\n : undefined\n : runtimeKeys[runtimeKeys.length - 1];\n const sortedKeys = keys\n .filter((key) => key !== pinnedLastKey)\n .sort(compareStrings);\n const orderedKeys =\n pinnedLastKey === undefined ? sortedKeys : [...sortedKeys, pinnedLastKey];\n const reorderedRuntimeKeys = toRuntimeKeyOrder(orderedKeys);\n\n // Integer-like keys always iterate first: bail out if that moves the\n // implicit fallback.\n if (\n !hasNamedFallback &&\n reorderedRuntimeKeys[reorderedRuntimeKeys.length - 1] !== pinnedLastKey\n ) {\n return undefined;\n }\n\n return orderedKeys;\n};\n"],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_keyOrderings = require("./keyOrderings.cjs");
3
+ const require_sortContentNodes = require("./sortContentNodes.cjs");
4
+
5
+ // src/sortContentKeys.ts
6
+ /** `*.content.ts`, `*.content.json`, … — the Intlayer declaration files. */
7
+ const CONTENT_FILE_PATTERN = /\.content\.(?:[cm]?[jt]sx?|json5?|jsonc)$/;
8
+ /** Expression wrappers looked through: `x as T`, `x satisfies T`, `(x)`, `x!`. */
9
+ const EXPRESSION_WRAPPER_TYPES = /* @__PURE__ */ new Set([
10
+ "TSAsExpression",
11
+ "TSSatisfiesExpression",
12
+ "TSNonNullExpression",
13
+ "TSTypeAssertion",
14
+ "TypeCastExpression",
15
+ "ParenthesizedExpression"
16
+ ]);
17
+ /** True when Prettier is formatting an Intlayer content declaration file. */
18
+ const isContentFile = (filePath) => typeof filePath === "string" && CONTENT_FILE_PATTERN.test(filePath);
19
+ const getPropertyValues = (objectNode) => objectNode.properties.filter(require_sortContentNodes.isPlainProperty).map((property) => property.value);
20
+ /**
21
+ * Sorts a content value alphabetically, recursing through nested objects,
22
+ * arrays and the arguments of every helper call. The maps of `t()` /
23
+ * `plural()` / `enu()` / `select()` keep their own ordering; the argument
24
+ * objects of other helpers keep theirs.
25
+ */
26
+ const sortContentValue = (value) => {
27
+ if (!require_sortContentNodes.isAstNode(value)) return;
28
+ if (EXPRESSION_WRAPPER_TYPES.has(value.type)) {
29
+ sortContentValue(value.expression);
30
+ return;
31
+ }
32
+ if (value.type === "ArrayExpression") {
33
+ for (const element of value.elements) sortContentValue(element);
34
+ return;
35
+ }
36
+ const helperContent = require_sortContentNodes.getHelperCallContent(value) ?? require_sortContentNodes.getSerializedNodeContent(value);
37
+ if (helperContent) {
38
+ for (const nestedValue of getPropertyValues(helperContent.content)) sortContentValue(nestedValue);
39
+ return;
40
+ }
41
+ if (value.type === "CallExpression") {
42
+ for (const argument of value.arguments) if (require_sortContentNodes.isObjectExpression(argument)) for (const nestedValue of getPropertyValues(argument)) sortContentValue(nestedValue);
43
+ else sortContentValue(argument);
44
+ return;
45
+ }
46
+ if (!require_sortContentNodes.isObjectExpression(value)) return;
47
+ if (!value.properties.some((property) => require_sortContentNodes.isPlainProperty(property) && require_sortContentNodes.getStaticKeyName(property) === "nodeType")) require_sortContentNodes.sortObjectProperties(value, require_keyOrderings.orderAlphabetically);
48
+ for (const nestedValue of getPropertyValues(value)) sortContentValue(nestedValue);
49
+ };
50
+ /**
51
+ * The `content` of a dictionary declaration: an object holding both a `key`
52
+ * and a `content` property.
53
+ */
54
+ const getDictionaryContent = (node) => {
55
+ if (!require_sortContentNodes.isObjectExpression(node)) return void 0;
56
+ const properties = node.properties.filter(require_sortContentNodes.isPlainProperty);
57
+ const findProperty = (name) => properties.find((property) => require_sortContentNodes.getStaticKeyName(property) === name);
58
+ if (!findProperty("key")) return void 0;
59
+ return findProperty("content")?.value;
60
+ };
61
+ /**
62
+ * Sorts, in place, the keys of every dictionary `content` object of the AST,
63
+ * and of their nested objects, alphabetically.
64
+ */
65
+ const sortContentKeysInAst = (rootNode) => {
66
+ const visitedNodes = /* @__PURE__ */ new WeakSet();
67
+ const pendingValues = [rootNode];
68
+ while (pendingValues.length > 0) {
69
+ const value = pendingValues.pop();
70
+ if (typeof value !== "object" || value === null) continue;
71
+ if (visitedNodes.has(value)) continue;
72
+ visitedNodes.add(value);
73
+ if (require_sortContentNodes.isAstNode(value)) {
74
+ const dictionaryContent = getDictionaryContent(value);
75
+ if (dictionaryContent) {
76
+ sortContentValue(dictionaryContent);
77
+ continue;
78
+ }
79
+ }
80
+ for (const [field, child] of Object.entries(value)) if (field !== "loc" && field !== "range" && field !== "parent") pendingValues.push(child);
81
+ }
82
+ };
83
+
84
+ exports.isContentFile = isContentFile;
85
+ exports.sortContentKeysInAst = sortContentKeysInAst;
86
+ //# sourceMappingURL=sortContentKeys.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sortContentKeys.cjs","names":["isPlainProperty","isAstNode","getHelperCallContent","getSerializedNodeContent","isObjectExpression","getStaticKeyName","orderAlphabetically"],"sources":["../../src/sortContentKeys.ts"],"sourcesContent":["import { orderAlphabetically } from './keyOrderings';\nimport {\n type AstNode,\n getHelperCallContent,\n getSerializedNodeContent,\n getStaticKeyName,\n isAstNode,\n isObjectExpression,\n isPlainProperty,\n sortObjectProperties,\n} from './sortContentNodes';\n\n/** `*.content.ts`, `*.content.json`, … — the Intlayer declaration files. */\nconst CONTENT_FILE_PATTERN = /\\.content\\.(?:[cm]?[jt]sx?|json5?|jsonc)$/;\n\n/** Expression wrappers looked through: `x as T`, `x satisfies T`, `(x)`, `x!`. */\nconst EXPRESSION_WRAPPER_TYPES = new Set([\n 'TSAsExpression',\n 'TSSatisfiesExpression',\n 'TSNonNullExpression',\n 'TSTypeAssertion',\n 'TypeCastExpression',\n 'ParenthesizedExpression',\n]);\n\n/** True when Prettier is formatting an Intlayer content declaration file. */\nexport const isContentFile = (filePath: string | undefined): boolean =>\n typeof filePath === 'string' && CONTENT_FILE_PATTERN.test(filePath);\n\nconst getPropertyValues = (objectNode: AstNode): unknown[] =>\n (objectNode.properties as unknown[])\n .filter(isPlainProperty)\n .map((property) => property.value);\n\n/**\n * Sorts a content value alphabetically, recursing through nested objects,\n * arrays and the arguments of every helper call. The maps of `t()` /\n * `plural()` / `enu()` / `select()` keep their own ordering; the argument\n * objects of other helpers keep theirs.\n */\nconst sortContentValue = (value: unknown): void => {\n if (!isAstNode(value)) return;\n\n if (EXPRESSION_WRAPPER_TYPES.has(value.type)) {\n sortContentValue(value.expression);\n return;\n }\n\n if (value.type === 'ArrayExpression') {\n for (const element of value.elements as unknown[]) {\n sortContentValue(element);\n }\n return;\n }\n\n const helperContent =\n getHelperCallContent(value) ?? getSerializedNodeContent(value);\n\n if (helperContent) {\n for (const nestedValue of getPropertyValues(helperContent.content)) {\n sortContentValue(nestedValue);\n }\n return;\n }\n\n // Any other helper (`cond`, `gender`, `md`, `insert`, `nest`, `file`, …):\n // its argument object may carry a meaningful key order (the last `cond` /\n // `gender` key is the fallback), so only the values inside are sorted.\n if (value.type === 'CallExpression') {\n for (const argument of value.arguments as unknown[]) {\n if (isObjectExpression(argument)) {\n for (const nestedValue of getPropertyValues(argument)) {\n sortContentValue(nestedValue);\n }\n } else {\n sortContentValue(argument);\n }\n }\n return;\n }\n\n if (!isObjectExpression(value)) return;\n\n // An unknown serialized node (`{ nodeType: 'condition', … }`) is not a plain\n // content object: its fields are not reordered.\n const isSerializedNode = (value.properties as unknown[]).some(\n (property) =>\n isPlainProperty(property) && getStaticKeyName(property) === 'nodeType'\n );\n\n if (!isSerializedNode) sortObjectProperties(value, orderAlphabetically);\n\n for (const nestedValue of getPropertyValues(value)) {\n sortContentValue(nestedValue);\n }\n};\n\n/**\n * The `content` of a dictionary declaration: an object holding both a `key`\n * and a `content` property.\n */\nconst getDictionaryContent = (node: AstNode): unknown => {\n if (!isObjectExpression(node)) return undefined;\n\n const properties = (node.properties as unknown[]).filter(isPlainProperty);\n const findProperty = (name: string) =>\n properties.find((property) => getStaticKeyName(property) === name);\n\n if (!findProperty('key')) return undefined;\n\n return findProperty('content')?.value;\n};\n\n/**\n * Sorts, in place, the keys of every dictionary `content` object of the AST,\n * and of their nested objects, alphabetically.\n */\nexport const sortContentKeysInAst = (rootNode: unknown): void => {\n const visitedNodes = new WeakSet<object>();\n const pendingValues: unknown[] = [rootNode];\n\n while (pendingValues.length > 0) {\n const value = pendingValues.pop();\n\n if (typeof value !== 'object' || value === null) continue;\n if (visitedNodes.has(value)) continue;\n\n visitedNodes.add(value);\n\n if (isAstNode(value)) {\n const dictionaryContent = getDictionaryContent(value);\n\n if (dictionaryContent) {\n sortContentValue(dictionaryContent);\n continue;\n }\n }\n\n for (const [field, child] of Object.entries(value)) {\n if (field !== 'loc' && field !== 'range' && field !== 'parent') {\n pendingValues.push(child);\n }\n }\n }\n};\n"],"mappings":";;;;;;AAaA,MAAM,uBAAuB;;AAG7B,MAAM,2CAA2B,IAAI,IAAI;CACvC;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,MAAa,iBAAiB,aAC5B,OAAO,aAAa,YAAY,qBAAqB,KAAK,QAAQ;AAEpE,MAAM,qBAAqB,eACxB,WAAW,WACT,OAAOA,wCAAe,CAAC,CACvB,KAAK,aAAa,SAAS,KAAK;;;;;;;AAQrC,MAAM,oBAAoB,UAAyB;CACjD,IAAI,CAACC,mCAAU,KAAK,GAAG;CAEvB,IAAI,yBAAyB,IAAI,MAAM,IAAI,GAAG;EAC5C,iBAAiB,MAAM,UAAU;EACjC;CACF;CAEA,IAAI,MAAM,SAAS,mBAAmB;EACpC,KAAK,MAAM,WAAW,MAAM,UAC1B,iBAAiB,OAAO;EAE1B;CACF;CAEA,MAAM,gBACJC,8CAAqB,KAAK,KAAKC,kDAAyB,KAAK;CAE/D,IAAI,eAAe;EACjB,KAAK,MAAM,eAAe,kBAAkB,cAAc,OAAO,GAC/D,iBAAiB,WAAW;EAE9B;CACF;CAKA,IAAI,MAAM,SAAS,kBAAkB;EACnC,KAAK,MAAM,YAAY,MAAM,WAC3B,IAAIC,4CAAmB,QAAQ,GAC7B,KAAK,MAAM,eAAe,kBAAkB,QAAQ,GAClD,iBAAiB,WAAW;OAG9B,iBAAiB,QAAQ;EAG7B;CACF;CAEA,IAAI,CAACA,4CAAmB,KAAK,GAAG;CAShC,IAAI,CALsB,MAAM,WAAyB,MACtD,aACCJ,yCAAgB,QAAQ,KAAKK,0CAAiB,QAAQ,MAAM,UAG5C,GAAG,8CAAqB,OAAOC,wCAAmB;CAEtE,KAAK,MAAM,eAAe,kBAAkB,KAAK,GAC/C,iBAAiB,WAAW;AAEhC;;;;;AAMA,MAAM,wBAAwB,SAA2B;CACvD,IAAI,CAACF,4CAAmB,IAAI,GAAG,OAAO;CAEtC,MAAM,aAAc,KAAK,WAAyB,OAAOJ,wCAAe;CACxE,MAAM,gBAAgB,SACpB,WAAW,MAAM,aAAaK,0CAAiB,QAAQ,MAAM,IAAI;CAEnE,IAAI,CAAC,aAAa,KAAK,GAAG,OAAO;CAEjC,OAAO,aAAa,SAAS,CAAC,EAAE;AAClC;;;;;AAMA,MAAa,wBAAwB,aAA4B;CAC/D,MAAM,+BAAe,IAAI,QAAgB;CACzC,MAAM,gBAA2B,CAAC,QAAQ;CAE1C,OAAO,cAAc,SAAS,GAAG;EAC/B,MAAM,QAAQ,cAAc,IAAI;EAEhC,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EACjD,IAAI,aAAa,IAAI,KAAK,GAAG;EAE7B,aAAa,IAAI,KAAK;EAEtB,IAAIJ,mCAAU,KAAK,GAAG;GACpB,MAAM,oBAAoB,qBAAqB,KAAK;GAEpD,IAAI,mBAAmB;IACrB,iBAAiB,iBAAiB;IAClC;GACF;EACF;EAEA,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,KAAK,GAC/C,IAAI,UAAU,SAAS,UAAU,WAAW,UAAU,UACpD,cAAc,KAAK,KAAK;CAG9B;AACF"}