@gooddata/i18n-toolkit 11.54.0-alpha.4 → 11.54.0-alpha.5

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=checkTranslations.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkTranslations.test.d.ts","sourceRoot":"","sources":["../../src/validations/checkTranslations.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,220 @@
1
+ // (C) 2020-2026 GoodData Corporation
2
+ import { describe, expect, it } from "vitest";
3
+ import { checkTranslations } from "./usage/checkTranslations.js";
4
+ describe("check translations", () => {
5
+ it("valid, extracted and loaded are same", () => {
6
+ const { uncontrolled, results } = checkTranslations([TestJsonBasic], [PatternAll], createExtracted(["ok.key", "fail.key"]));
7
+ expect(uncontrolled).toEqual([]);
8
+ expect(results.length).toEqual(1);
9
+ checkResults(results[0], {
10
+ files: ["defaults/en-US/test.json"],
11
+ identifier: "/.+/",
12
+ extracted: 2,
13
+ loaded: 2,
14
+ });
15
+ });
16
+ it("invalid, only 1 extracted message, but loaded 2", () => {
17
+ const { uncontrolled, results } = checkTranslations([TestJsonBasic], [PatternAll], createExtracted(["ok.key"]));
18
+ expect(uncontrolled).toEqual([]);
19
+ expect(results.length).toEqual(1);
20
+ checkResults(results[0], {
21
+ files: ["defaults/en-US/test.json"],
22
+ identifier: "/.+/",
23
+ extracted: 1,
24
+ loaded: 2,
25
+ unused: 1,
26
+ unusedMessages: ["fail.key"],
27
+ });
28
+ });
29
+ it("invalid, extracted 3 message, but loaded 2", () => {
30
+ const { uncontrolled, results } = checkTranslations([TestJsonBasic], [PatternAll], createExtracted(["ok.key", "fail.key", "fail.key1"]));
31
+ expect(uncontrolled).toEqual([]);
32
+ expect(results.length).toEqual(1);
33
+ checkResults(results[0], {
34
+ files: ["defaults/en-US/test.json"],
35
+ identifier: "/.+/",
36
+ extracted: 3,
37
+ loaded: 2,
38
+ missing: 1,
39
+ missingMessages: ["fail.key1"],
40
+ });
41
+ });
42
+ it("valid with filtered, extracted and loaded are same", () => {
43
+ const { uncontrolled, results } = checkTranslations([TestJsonBasicWithIgnore], [PatternOkAndFail_Filtered], createExtracted(["ok.key", "fail.key"]));
44
+ expect(uncontrolled).toEqual([]);
45
+ expect(results.length).toEqual(1);
46
+ checkResults(results[0], {
47
+ files: ["defaults/en-US/test.json"],
48
+ identifier: "/^ok\\./,/^fail\\./",
49
+ extracted: 2,
50
+ loaded: 2,
51
+ });
52
+ });
53
+ it("valid with filtered, extracted and loaded are same and there is uncontrolled message", () => {
54
+ const { uncontrolled, results } = checkTranslations([TestJsonBasicWithIgnore], [PatternOkAndFail_Filtered], createExtracted(["ok.key", "fail.key", "ignore.key1"]));
55
+ expect(uncontrolled).toEqual(["ignore.key1"]);
56
+ expect(results.length).toEqual(1);
57
+ checkResults(results[0], {
58
+ files: ["defaults/en-US/test.json"],
59
+ identifier: "/^ok\\./,/^fail\\./",
60
+ extracted: 2,
61
+ loaded: 2,
62
+ });
63
+ });
64
+ it("valid with filtered, extracted and loaded are same and there are ignored messages", () => {
65
+ const { uncontrolled, results } = checkTranslations([TestJsonBasicWithIgnore], [PatternOkAndFail_Filtered, PatternIgnore], createExtracted(["ok.key", "fail.key", "ignored.key1"]));
66
+ expect(uncontrolled).toEqual([]);
67
+ expect(results.length).toEqual(2);
68
+ checkResults(results[0], {
69
+ files: ["defaults/en-US/test.json"],
70
+ identifier: "/^ok\\./,/^fail\\./",
71
+ extracted: 2,
72
+ loaded: 2,
73
+ });
74
+ checkResults(results[1], {
75
+ files: ["defaults/en-US/test.json"],
76
+ identifier: "/^ignored\\./",
77
+ ignore: true,
78
+ extracted: 1,
79
+ loaded: 3,
80
+ ignored: 3,
81
+ ignoredMessages: ["ignored.key1", "ignored.key2", "ignored.key3"],
82
+ });
83
+ });
84
+ it("valid, extracted and loaded are same and there are ignored messages", () => {
85
+ const { uncontrolled, results } = checkTranslations([TestJsonBasicWithIgnore], [PatternAll, PatternIgnore], createExtracted(["ok.key", "fail.key"]));
86
+ expect(uncontrolled).toEqual([]);
87
+ expect(results.length).toEqual(2);
88
+ checkResults(results[0], {
89
+ files: ["defaults/en-US/test.json"],
90
+ identifier: "/.+/",
91
+ extracted: 2,
92
+ loaded: 2,
93
+ });
94
+ checkResults(results[1], {
95
+ files: ["defaults/en-US/test.json"],
96
+ identifier: "/^ignored\\./",
97
+ ignore: true,
98
+ loaded: 3,
99
+ ignored: 3,
100
+ ignoredMessages: ["ignored.key1", "ignored.key2", "ignored.key3"],
101
+ });
102
+ });
103
+ it("invalid, extracted more messages than loaded", () => {
104
+ const { uncontrolled, results } = checkTranslations([TestJsonBasic], [PatternAll, PatternIgnore], createExtracted(["ok.key", "fail.key", "ignored.key1", "ignored.key2", "ignored.key3"]));
105
+ expect(uncontrolled).toEqual([]);
106
+ expect(results.length).toEqual(2);
107
+ checkResults(results[0], {
108
+ files: ["defaults/en-US/test.json"],
109
+ identifier: "/.+/",
110
+ extracted: 5,
111
+ loaded: 2,
112
+ missing: 3,
113
+ missingMessages: ["ignored.key1", "ignored.key2", "ignored.key3"],
114
+ });
115
+ checkResults(results[1], {
116
+ files: ["defaults/en-US/test.json"],
117
+ identifier: "/^ignored\\./",
118
+ ignore: true,
119
+ });
120
+ });
121
+ it("valid, extracted and loaded are same because of ignored messages for all", () => {
122
+ const { uncontrolled, results } = checkTranslations([TestJsonBasicWithIgnore, TestJsonBasicWithIgnore2], [PatternAll, PatternIgnore], createExtracted(["ok.key", "fail.key"]));
123
+ expect(uncontrolled).toEqual([]);
124
+ expect(results.length).toEqual(2);
125
+ checkResults(results[0], {
126
+ files: ["defaults/en-US/test.json", "extended/en-US/test.json"],
127
+ identifier: "/.+/",
128
+ extracted: 2,
129
+ loaded: 2,
130
+ });
131
+ checkResults(results[1], {
132
+ files: ["defaults/en-US/test.json", "extended/en-US/test.json"],
133
+ identifier: "/^ignored\\./",
134
+ ignore: true,
135
+ loaded: 3,
136
+ ignored: 3,
137
+ ignoredMessages: ["ignored.key1", "ignored.key2", "ignored.key3"],
138
+ });
139
+ });
140
+ it("invalid, extracted and loaded are not same because of ignored messages for only extended/ directory", () => {
141
+ const { uncontrolled, results } = checkTranslations([TestJsonBasicWithIgnore, TestJsonBasicWithIgnore2], [PatternAll, PatternIgnoreOnlyExtended], createExtracted(["ok.key", "fail.key"]));
142
+ expect(uncontrolled).toEqual([]);
143
+ expect(results.length).toEqual(2);
144
+ checkResults(results[0], {
145
+ files: ["defaults/en-US/test.json", "extended/en-US/test.json"],
146
+ identifier: "/.+/",
147
+ extracted: 2,
148
+ loaded: 5,
149
+ unused: 3,
150
+ unusedMessages: ["ignored.key1", "ignored.key2", "ignored.key3"],
151
+ });
152
+ checkResults(results[1], {
153
+ files: ["extended/en-US/test.json"],
154
+ identifier: "/^ignored\\./",
155
+ ignore: true,
156
+ loaded: 3,
157
+ ignored: 3,
158
+ ignoredMessages: ["ignored.key1", "ignored.key2", "ignored.key3"],
159
+ });
160
+ });
161
+ });
162
+ function checkResults(result, { files, identifier, missingMessages = [], unusedMessages = [], ignoredMessages = [], ignore = undefined, loaded = 0, missing = 0, extracted = 0, unused = 0, ignored = 0, }) {
163
+ expect(result.data.missingMessages).toEqual(missingMessages);
164
+ expect(result.data.unusedMessages).toEqual(unusedMessages);
165
+ expect(result.data.ignoredMessages).toEqual(ignoredMessages);
166
+ expect(result.files).toEqual(files);
167
+ expect(result.identifier).toEqual(identifier);
168
+ expect(result.ignore).toEqual(ignore);
169
+ expect(result.stats.extracted).toBe(extracted);
170
+ expect(result.stats.loaded).toBe(loaded);
171
+ expect(result.stats.missing).toBe(missing);
172
+ expect(result.stats.unused).toBe(unused);
173
+ expect(result.stats.ignored).toBe(ignored);
174
+ }
175
+ function createExtracted(arr) {
176
+ return arr.reduce((prev, item) => ({ ...prev, [item]: true }), {});
177
+ }
178
+ //FILES
179
+ const TestJsonBasic = [
180
+ "defaults/en-US/test.json",
181
+ {
182
+ "ok.key": { text: "OK", crowdinContext: "" },
183
+ "fail.key": { text: "FAIL", crowdinContext: "" },
184
+ },
185
+ ];
186
+ const TestJsonBasicWithIgnore = [
187
+ "defaults/en-US/test.json",
188
+ {
189
+ ...TestJsonBasic[1],
190
+ "ignored.key1": { text: "Ignore1", crowdinContext: "" },
191
+ "ignored.key2": { text: "Ignore2", crowdinContext: "" },
192
+ "ignored.key3": { text: "Ignore3", crowdinContext: "" },
193
+ },
194
+ ];
195
+ const TestJsonBasicWithIgnore2 = [
196
+ "extended/en-US/test.json",
197
+ {
198
+ ...TestJsonBasic[1],
199
+ "ignored.key1": { text: "Ignore1", crowdinContext: "" },
200
+ "ignored.key2": { text: "Ignore2", crowdinContext: "" },
201
+ "ignored.key3": { text: "Ignore3", crowdinContext: "" },
202
+ },
203
+ ];
204
+ //PATTERNS
205
+ const PatternAll = {
206
+ pattern: [/.+/],
207
+ };
208
+ const PatternOkAndFail_Filtered = {
209
+ pattern: [/^ok\./, /^fail\./],
210
+ filterTranslationFile: true,
211
+ };
212
+ const PatternIgnore = {
213
+ pattern: [/^ignored\./],
214
+ ignore: true,
215
+ };
216
+ const PatternIgnoreOnlyExtended = {
217
+ dir: /^extended/,
218
+ pattern: [/^ignored\./],
219
+ ignore: true,
220
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=htmlSyntax.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"htmlSyntax.test.d.ts","sourceRoot":"","sources":["../../src/validations/htmlSyntax.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ // (C) 2020-2026 GoodData Corporation
2
+ import { describe, expect, it } from "vitest";
3
+ import { suppressConsole } from "@gooddata/util";
4
+ import { getHtmlSyntaxCheck } from "./htmlSyntax.js";
5
+ function createLocalizations(value, key = "test.key", file = "test.json") {
6
+ return [[file, { [key]: value }]];
7
+ }
8
+ describe("validate html message tests", () => {
9
+ const scenarios = [
10
+ ["simple text", "This is message", null],
11
+ ["simple html", "This is <b>message</b>", null],
12
+ [
13
+ "simple invalid html non sense",
14
+ "This is <bmessage</b>",
15
+ `Html format of localizations is not correct`,
16
+ ],
17
+ ];
18
+ it.each(scenarios)("validate %s", async (_, msg, err) => {
19
+ if (err) {
20
+ await expect(suppressConsole(() => getHtmlSyntaxCheck(createLocalizations(msg)), "error", [
21
+ { type: "startsWith", value: "✘" },
22
+ ])).rejects.toThrowError(err);
23
+ }
24
+ else {
25
+ await expect(getHtmlSyntaxCheck(createLocalizations(msg))).resolves.not.toThrow();
26
+ }
27
+ });
28
+ it("includes key and file in error message", async () => {
29
+ const localizations = createLocalizations("This is <bmessage</b>", "my.html.key", "/path/to/en-US.json");
30
+ await expect(suppressConsole(() => getHtmlSyntaxCheck(localizations), "error", [
31
+ { type: "startsWith", value: "✘" },
32
+ ])).rejects.toThrowError(/Key: "my\.html\.key"/);
33
+ });
34
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=messageFormat.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messageFormat.test.d.ts","sourceRoot":"","sources":["../../src/validations/messageFormat.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ // (C) 2020-2026 GoodData Corporation
2
+ import { describe, expect, it } from "vitest";
3
+ import { suppressConsole } from "@gooddata/util";
4
+ import { getIntlMessageFormatCheck } from "./messageFormat.js";
5
+ function createLocalizations(value, key = "test.key", file = "test.json") {
6
+ return [[file, { [key]: value }]];
7
+ }
8
+ describe("validate ICU message tests", () => {
9
+ const scenarios = [
10
+ ["simple text", "This is message", null],
11
+ ["simple text with marks", "This is <strong>message</strong>", null],
12
+ ["simple text with ICU", "The is {count} {count, plural, one {one} other {mores}}.", null],
13
+ [
14
+ "simple text with invalid ICU keyword",
15
+ "The is {count} {count, plural, one {one} othr {mores}.",
16
+ `Intl format of localizations is not correct`,
17
+ ],
18
+ [
19
+ "simple text with invalid ICU }",
20
+ "The is {count} {count, plural, one {one} other {mores}.",
21
+ `Intl format of localizations is not correct`,
22
+ ],
23
+ ];
24
+ it.each(scenarios)("validate %s", async (_, msg, err) => {
25
+ if (err) {
26
+ await expect(suppressConsole(() => getIntlMessageFormatCheck(createLocalizations(msg)), "error", [
27
+ {
28
+ type: "startsWith",
29
+ value: "✘",
30
+ },
31
+ ])).rejects.toThrowError(err);
32
+ }
33
+ else {
34
+ await expect(getIntlMessageFormatCheck(createLocalizations(msg))).resolves.not.toThrow();
35
+ }
36
+ });
37
+ it("includes key and file in error message", async () => {
38
+ const localizations = createLocalizations("The is {count} {count, plural, one {one} othr {mores}.", "my.translation.key", "/path/to/en-US.json");
39
+ await expect(suppressConsole(() => getIntlMessageFormatCheck(localizations), "error", [
40
+ {
41
+ type: "startsWith",
42
+ value: "✘",
43
+ },
44
+ ])).rejects.toThrowError(/Key: "my\.translation\.key"/);
45
+ });
46
+ it("collects all errors before throwing", async () => {
47
+ const localizations = [
48
+ [
49
+ "en-US.json",
50
+ {
51
+ "error.one": "Invalid {count, plural, one {one} othr {mores}.",
52
+ "valid.key": "This is valid",
53
+ "error.two": "Another invalid {missing",
54
+ },
55
+ ],
56
+ ];
57
+ await expect(suppressConsole(() => getIntlMessageFormatCheck(localizations), "error", [
58
+ {
59
+ type: "startsWith",
60
+ value: "✘",
61
+ },
62
+ ])).rejects.toThrowError(/error\.one[\s\S]*error\.two/);
63
+ });
64
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=structure.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structure.test.d.ts","sourceRoot":"","sources":["../../src/validations/structure.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ // (C) 2020-2026 GoodData Corporation
2
+ import { describe, expect, it } from "vitest";
3
+ import { suppressConsole } from "@gooddata/util";
4
+ import { getStructureCheck } from "./structure.js";
5
+ describe("validate structure tests", () => {
6
+ const scenarios = [
7
+ [
8
+ "basic format with required only",
9
+ { "message.id": { text: "This is value", crowdinContext: "This is comment" } },
10
+ null,
11
+ ],
12
+ ["basic format with string", { "message.id": "This is value" }, null],
13
+ [
14
+ "basic format with all props",
15
+ {
16
+ "message.id": {
17
+ text: "This is value",
18
+ crowdinContext: "This is comment",
19
+ translate: false,
20
+ },
21
+ },
22
+ null,
23
+ ],
24
+ [
25
+ "basic format with more props",
26
+ {
27
+ "message.id": {
28
+ text: "This is value",
29
+ crowdinContext: "This is comment",
30
+ translate: false,
31
+ limit: 2,
32
+ },
33
+ },
34
+ `Structure of localizations is not correct`,
35
+ ],
36
+ [
37
+ "basic format with missing comment",
38
+ {
39
+ "message.id": {
40
+ text: "This is value",
41
+ },
42
+ },
43
+ `Structure of localizations is not correct`,
44
+ ],
45
+ [
46
+ "basic format with missing value",
47
+ {
48
+ "message.id": {
49
+ crowdinContext: "",
50
+ },
51
+ },
52
+ `Structure of localizations is not correct`,
53
+ ],
54
+ ];
55
+ it.each(scenarios)("validate %s", async (_, structure, err) => {
56
+ if (err) {
57
+ await expect(suppressConsole(() => getStructureCheck([["en-US.json", structure]]), "error", [
58
+ {
59
+ type: "startsWith",
60
+ value: "✘",
61
+ },
62
+ ])).rejects.toThrowError(err);
63
+ }
64
+ else {
65
+ await expect(getStructureCheck([["en-US.json", structure]])).resolves.not.toThrow();
66
+ }
67
+ });
68
+ it("includes file in error message", async () => {
69
+ const structure = {
70
+ "message.id": {
71
+ text: "This is value",
72
+ },
73
+ };
74
+ await expect(suppressConsole(() => getStructureCheck([["/path/to/en-US.json", structure]]), "error", [
75
+ { type: "startsWith", value: "✘" },
76
+ ])).rejects.toThrowError(/File: \/path\/to\/en-US\.json/);
77
+ });
78
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/i18n-toolkit",
3
- "version": "11.54.0-alpha.4",
3
+ "version": "11.54.0-alpha.5",
4
4
  "description": "Localization validator to validate localization complexity and intl and html format.",
5
5
  "license": "MIT",
6
6
  "author": "GoodData",
@@ -34,7 +34,7 @@
34
34
  "lodash-es": "^4.17.23",
35
35
  "tslib": "2.8.1",
36
36
  "util": "^0.12.3",
37
- "@gooddata/util": "11.54.0-alpha.4"
37
+ "@gooddata/util": "11.54.0-alpha.5"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/lodash-es": "^4.17.12",
@@ -59,8 +59,8 @@
59
59
  "oxlint-tsgolint": "0.15.0",
60
60
  "typescript": "5.9.3",
61
61
  "vitest": "4.1.8",
62
- "@gooddata/eslint-config": "11.54.0-alpha.4",
63
- "@gooddata/oxlint-config": "11.54.0-alpha.4"
62
+ "@gooddata/oxlint-config": "11.54.0-alpha.5",
63
+ "@gooddata/eslint-config": "11.54.0-alpha.5"
64
64
  },
65
65
  "scripts": {
66
66
  "_phase:build": "npm run build",