ilib-lint 2.9.1 → 2.9.2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ilib-lint",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"module": "./src/index.js",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"micromatch": "^4.0.7",
|
|
72
72
|
"options-parser": "^0.4.0",
|
|
73
73
|
"xml-js": "^1.6.11",
|
|
74
|
-
"ilib-lint-common": "^3.2.0",
|
|
75
74
|
"ilib-common": "^1.1.6",
|
|
75
|
+
"ilib-lint-common": "^3.2.0",
|
|
76
76
|
"ilib-locale": "^1.2.4",
|
|
77
|
-
"ilib-tools-common": "^1.
|
|
77
|
+
"ilib-tools-common": "^1.15.0"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"coverage": "pnpm test -- --coverage",
|
package/src/Project.js
CHANGED
|
@@ -324,9 +324,10 @@ class Project extends DirItem {
|
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
|
-
|
|
327
|
+
const formatterName = this.options?.opt?.formatter || this.options.formatter || "ansi-console-formatter";
|
|
328
|
+
this.formatter = fmtMgr.get(formatterName);
|
|
328
329
|
if (!this.formatter) {
|
|
329
|
-
logger.error(`Could not find formatter ${
|
|
330
|
+
logger.error(`Could not find formatter ${formatterName}. Aborting...`);
|
|
330
331
|
process.exit(3);
|
|
331
332
|
}
|
|
332
333
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ResourceICUPluralTranslation.js - rule to check formatjs/ICU style plurals
|
|
3
3
|
* in the target string actually have translations
|
|
4
4
|
*
|
|
5
|
-
* Copyright © 2023-
|
|
5
|
+
* Copyright © 2023-2025 JEDLSoft
|
|
6
6
|
*
|
|
7
7
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
8
|
* you may not use this file except in compliance with the License.
|
|
@@ -100,6 +100,25 @@ class ResourceICUPluralTranslation extends ResourceRule {
|
|
|
100
100
|
return result;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Reconstruct the string but only give the text nodes of the given tree so we can
|
|
105
|
+
* see if there is anything to translate.
|
|
106
|
+
* @private
|
|
107
|
+
* @param {Object} nodes the top of the tree to reconstruct
|
|
108
|
+
* @returns {string} the text of the tree
|
|
109
|
+
*/
|
|
110
|
+
textNodes(nodes) {
|
|
111
|
+
let result = "";
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
114
|
+
if (nodes[i].type === 0) {
|
|
115
|
+
result += nodes[i].value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return result.trim();
|
|
120
|
+
}
|
|
121
|
+
|
|
103
122
|
/**
|
|
104
123
|
* Traverse an array of ast nodes to find any embedded selects or plurals
|
|
105
124
|
* or tags, and then process those separately.
|
|
@@ -144,6 +163,11 @@ class ResourceICUPluralTranslation extends ResourceRule {
|
|
|
144
163
|
const sourcePluralCat = sourcePlural.options[sourceCategory];
|
|
145
164
|
if (!sourcePluralCat) return; // nothing to check!
|
|
146
165
|
|
|
166
|
+
// Only compare the source and target if there is some text there to
|
|
167
|
+
// translate. This will avoid the false positives for the situation where
|
|
168
|
+
// the only thing in the plural category string is just a {variable}.
|
|
169
|
+
if (this.textNodes(sourcePluralCat.value).length === 0) return;
|
|
170
|
+
|
|
147
171
|
const sourceStr = this.reconstruct(sourcePluralCat.value).replace(/\s+/g, " ").trim();
|
|
148
172
|
const targetStr = this.reconstruct(targetPlural.options[category].value).replace(/\s+/g, " ").trim();
|
|
149
173
|
let result = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* ResourceICUPlurals.js - rule to check formatjs/ICU style plurals in the target string
|
|
3
3
|
*
|
|
4
|
-
* Copyright © 2022-2023 JEDLSoft
|
|
4
|
+
* Copyright © 2022-2023, 2025 JEDLSoft
|
|
5
5
|
*
|
|
6
6
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
* you may not use this file except in compliance with the License.
|
|
@@ -20,34 +20,42 @@
|
|
|
20
20
|
import { IntlMessageFormat } from 'intl-messageformat';
|
|
21
21
|
import Locale from 'ilib-locale';
|
|
22
22
|
import { Result } from 'ilib-lint-common';
|
|
23
|
+
import { getLanguagePluralCategories } from 'ilib-tools-common';
|
|
23
24
|
|
|
24
25
|
import ResourceRule from './ResourceRule.js';
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Get the difference of two sets. That is, return a set that contains
|
|
29
|
+
* all the items in set1 that are not in set2.
|
|
30
|
+
* @private
|
|
31
|
+
* @param {Set<string>} set1 The first set
|
|
32
|
+
* @param {Set<string>} set2 The second set
|
|
33
|
+
* @returns {Set<string>} The difference of the two sets
|
|
34
|
+
*/
|
|
35
|
+
function difference(set1, set2) {
|
|
36
|
+
const result = new Set();
|
|
37
|
+
set1.forEach(item => {
|
|
38
|
+
if (!set2.has(item)) {
|
|
39
|
+
result.add(item);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
28
44
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"be": [ "one", "few", "other" ],
|
|
44
|
-
"sr": [ "one", "few", "other" ],
|
|
45
|
-
"hr": [ "one", "few", "other" ],
|
|
46
|
-
"cs": [ "one", "few", "other" ],
|
|
47
|
-
"sk": [ "one", "few", "other" ],
|
|
48
|
-
"pl": [ "one", "few", "other" ],
|
|
49
|
-
"sl": [ "one", "two", "few", "other" ],
|
|
50
|
-
"ar": [ "zero", "one", "two", "few", "many", "other" ]
|
|
45
|
+
/**
|
|
46
|
+
* Get the union of two sets. That is, return a set that contains
|
|
47
|
+
* all the items in set1 and set2.
|
|
48
|
+
* @private
|
|
49
|
+
* @param {Set<string>} set1 The first set
|
|
50
|
+
* @param {Set<string>} set2 The second set
|
|
51
|
+
* @returns {Set<string>} The union of the two sets
|
|
52
|
+
*/
|
|
53
|
+
function union(set1, set2) {
|
|
54
|
+
const result = new Set(set1);
|
|
55
|
+
set2.forEach(item => {
|
|
56
|
+
result.add(item);
|
|
57
|
+
});
|
|
58
|
+
return result;
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/**
|
|
@@ -73,31 +81,27 @@ class ResourceICUPlurals extends ResourceRule {
|
|
|
73
81
|
// categories that are required according to the language rules
|
|
74
82
|
let requiredSourceCategories, requiredTargetCategories;
|
|
75
83
|
|
|
76
|
-
// categories that actually exist in the select which are required by the language rules
|
|
77
|
-
let actualRequiredSourceCategories = [], actualRequiredTargetCategories = [];
|
|
78
|
-
|
|
79
|
-
// categories that actually exist in the select which are not required by the language rules
|
|
80
|
-
let actualNonrequiredSourceCategories = [], actualNonrequiredTargetCategories = [];
|
|
81
|
-
|
|
82
84
|
if (sourceSelect.node.pluralType === "cardinal") {
|
|
83
|
-
requiredSourceCategories =
|
|
84
|
-
requiredTargetCategories =
|
|
85
|
+
requiredSourceCategories = new Set(getLanguagePluralCategories(srcLocale.getLanguage()));
|
|
86
|
+
requiredTargetCategories = new Set(getLanguagePluralCategories(locale.getLanguage()));
|
|
85
87
|
} else {
|
|
86
88
|
// for select or selectordinal, only the "other" category is required
|
|
87
|
-
requiredSourceCategories = [ "other" ];
|
|
88
|
-
requiredTargetCategories = [ "other" ];
|
|
89
|
+
requiredSourceCategories = new Set([ "other" ]);
|
|
90
|
+
requiredTargetCategories = new Set([ "other" ]);
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
const allSourceCategories = Object.keys(sourceSelect.node.options);
|
|
92
|
-
|
|
93
|
-
actualNonrequiredSourceCategories = allSourceCategories.filter(category => !requiredSourceCategories.includes(category));
|
|
93
|
+
const allSourceCategories = new Set(Object.keys(sourceSelect.node.options));
|
|
94
|
+
let actualNonrequiredSourceCategories = difference(allSourceCategories, requiredSourceCategories);
|
|
94
95
|
|
|
95
|
-
const allTargetCategories = Object.keys(targetSelect.node.options);
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
const allTargetCategories = new Set(Object.keys(targetSelect.node.options));
|
|
97
|
+
if (sourceSelect.node.pluralType !== "cardinal") {
|
|
98
|
+
// for select and selectordinal, the target should always have all of the same categories as the source
|
|
99
|
+
requiredTargetCategories = allSourceCategories;
|
|
100
|
+
}
|
|
101
|
+
let actualNonrequiredTargetCategories = difference(allTargetCategories, requiredTargetCategories);
|
|
98
102
|
|
|
99
103
|
// first check the required plural categories
|
|
100
|
-
let missing = requiredTargetCategories.filter(category => {
|
|
104
|
+
let missing = Array.from(requiredTargetCategories).filter(category => {
|
|
101
105
|
if (!targetSelect.node.options[category]) {
|
|
102
106
|
// if the required category doesn't exist in the target, check if it is required
|
|
103
107
|
// in the source. If it is required in the source and does not exist there, then
|
|
@@ -107,7 +111,7 @@ class ResourceICUPlurals extends ResourceRule {
|
|
|
107
111
|
// in the target. If it is not required in the source, then produce a result because
|
|
108
112
|
// it is required in the target language and it doesn't matter about the source
|
|
109
113
|
// language.
|
|
110
|
-
if (!requiredSourceCategories.
|
|
114
|
+
if (!requiredSourceCategories.has(category) || sourceSelect.node.options[category]) {
|
|
111
115
|
return true;
|
|
112
116
|
}
|
|
113
117
|
} else if (sourceSelect.node.options[category]) {
|
|
@@ -135,7 +139,7 @@ class ResourceICUPlurals extends ResourceRule {
|
|
|
135
139
|
let opts = {
|
|
136
140
|
severity: "error",
|
|
137
141
|
rule: this,
|
|
138
|
-
description: `Missing categories in target string: ${missing.join(", ")}. Expecting these: ${
|
|
142
|
+
description: `Missing categories in target string: ${missing.join(", ")}. Expecting these: ${Array.from(union(requiredTargetCategories, actualNonrequiredSourceCategories)).join(", ")}`,
|
|
139
143
|
id: resource.getKey(),
|
|
140
144
|
highlight: `Target: ${resource.getTarget()}<e0></e0>`,
|
|
141
145
|
pathName: resource.getPath(),
|
|
@@ -146,34 +150,37 @@ class ResourceICUPlurals extends ResourceRule {
|
|
|
146
150
|
}
|
|
147
151
|
|
|
148
152
|
// now deal with the missing non-required categories
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
153
|
+
if (sourceSelect.node.pluralType === "cardinal") {
|
|
154
|
+
missing = Array.from(actualNonrequiredSourceCategories).filter(category => {
|
|
155
|
+
// if it is in the source, but it is not required, it should also be in the target
|
|
156
|
+
// so give a warning
|
|
157
|
+
return !allTargetCategories.has(category);
|
|
158
|
+
});
|
|
159
|
+
if (missing.length) {
|
|
160
|
+
let opts = {
|
|
161
|
+
severity: "warning", // non-required categories get a warning
|
|
162
|
+
rule: this,
|
|
163
|
+
description: `Missing categories in target string: ${missing.join(", ")}. Expecting these: ${Array.from(union(requiredTargetCategories, actualNonrequiredSourceCategories)).join(", ")}`,
|
|
164
|
+
id: resource.getKey(),
|
|
165
|
+
highlight: `Target: ${resource.getTarget()}<e0></e0>`,
|
|
166
|
+
pathName: resource.getPath(),
|
|
167
|
+
source: resource.getSource(),
|
|
168
|
+
locale: resource.getTargetLocale()
|
|
169
|
+
};
|
|
170
|
+
problems.push(new Result(opts));
|
|
171
|
+
}
|
|
172
|
+
} // else the source categories are already required in the target, so we don't need to check them again
|
|
166
173
|
|
|
167
174
|
// now deal with non-required categories that are in the target but not the source
|
|
168
|
-
const extra = actualNonrequiredTargetCategories.filter(category => {
|
|
169
|
-
return !allSourceCategories.
|
|
175
|
+
const extra = Array.from(actualNonrequiredTargetCategories).filter(category => {
|
|
176
|
+
return !allSourceCategories.has(category);
|
|
170
177
|
});
|
|
171
178
|
if (extra.length) {
|
|
172
179
|
const highlight = resource.getTarget().replace(new RegExp(`(${extra.join("|")})\\s*\\{`, "g"), "<e0>$1</e0> {");
|
|
173
180
|
let opts = {
|
|
174
181
|
severity: "warning",
|
|
175
182
|
rule: this,
|
|
176
|
-
description: `Extra categories in target string: ${extra.join(", ")}. Expecting only these: ${
|
|
183
|
+
description: `Extra categories in target string: ${extra.join(", ")}. Expecting only these: ${Array.from(union(requiredTargetCategories, actualNonrequiredSourceCategories)).join(", ")}`,
|
|
177
184
|
id: resource.getKey(),
|
|
178
185
|
highlight: `Target: ${highlight}`,
|
|
179
186
|
pathName: resource.getPath(),
|
|
@@ -280,4 +287,4 @@ class ResourceICUPlurals extends ResourceRule {
|
|
|
280
287
|
}
|
|
281
288
|
}
|
|
282
289
|
|
|
283
|
-
export default ResourceICUPlurals;
|
|
290
|
+
export default ResourceICUPlurals;
|