@messagevisor/core 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +7 -0
  4. package/jest.config.js +8 -0
  5. package/lib/benchmark/index.d.ts +2 -0
  6. package/lib/benchmark/index.js +417 -0
  7. package/lib/benchmark/index.js.map +1 -0
  8. package/lib/builder/index.d.ts +70 -0
  9. package/lib/builder/index.js +831 -0
  10. package/lib/builder/index.js.map +1 -0
  11. package/lib/cli/index.d.ts +28 -0
  12. package/lib/cli/index.js +182 -0
  13. package/lib/cli/index.js.map +1 -0
  14. package/lib/config/index.d.ts +61 -0
  15. package/lib/config/index.js +255 -0
  16. package/lib/config/index.js.map +1 -0
  17. package/lib/create/index.d.ts +2 -0
  18. package/lib/create/index.js +405 -0
  19. package/lib/create/index.js.map +1 -0
  20. package/lib/datasource/filesystemAdapter.d.ts +44 -0
  21. package/lib/datasource/filesystemAdapter.js +424 -0
  22. package/lib/datasource/filesystemAdapter.js.map +1 -0
  23. package/lib/datasource/index.d.ts +39 -0
  24. package/lib/datasource/index.js +96 -0
  25. package/lib/datasource/index.js.map +1 -0
  26. package/lib/error.d.ts +6 -0
  27. package/lib/error.js +49 -0
  28. package/lib/error.js.map +1 -0
  29. package/lib/evaluate/cli.d.ts +8 -0
  30. package/lib/evaluate/cli.js +179 -0
  31. package/lib/evaluate/cli.js.map +1 -0
  32. package/lib/evaluate/index.d.ts +10 -0
  33. package/lib/evaluate/index.js +131 -0
  34. package/lib/evaluate/index.js.map +1 -0
  35. package/lib/examples/coerceExampleIsoDates.d.ts +12 -0
  36. package/lib/examples/coerceExampleIsoDates.js +81 -0
  37. package/lib/examples/coerceExampleIsoDates.js.map +1 -0
  38. package/lib/examples/index.d.ts +63 -0
  39. package/lib/examples/index.js +713 -0
  40. package/lib/examples/index.js.map +1 -0
  41. package/lib/exporter/index.d.ts +60 -0
  42. package/lib/exporter/index.js +610 -0
  43. package/lib/exporter/index.js.map +1 -0
  44. package/lib/find-duplicates/index.d.ts +41 -0
  45. package/lib/find-duplicates/index.js +297 -0
  46. package/lib/find-duplicates/index.js.map +1 -0
  47. package/lib/generate-code/index.d.ts +11 -0
  48. package/lib/generate-code/index.js +157 -0
  49. package/lib/generate-code/index.js.map +1 -0
  50. package/lib/generate-code/typescript.d.ts +14 -0
  51. package/lib/generate-code/typescript.js +307 -0
  52. package/lib/generate-code/typescript.js.map +1 -0
  53. package/lib/importer/index.d.ts +64 -0
  54. package/lib/importer/index.js +1092 -0
  55. package/lib/importer/index.js.map +1 -0
  56. package/lib/index.d.ts +18 -0
  57. package/lib/index.js +35 -0
  58. package/lib/index.js.map +1 -0
  59. package/lib/info/index.d.ts +17 -0
  60. package/lib/info/index.js +132 -0
  61. package/lib/info/index.js.map +1 -0
  62. package/lib/init/index.d.ts +30 -0
  63. package/lib/init/index.js +348 -0
  64. package/lib/init/index.js.map +1 -0
  65. package/lib/lint/index.d.ts +1 -0
  66. package/lib/lint/index.js +6 -0
  67. package/lib/lint/index.js.map +1 -0
  68. package/lib/linter/attributeSchema.d.ts +7 -0
  69. package/lib/linter/attributeSchema.js +36 -0
  70. package/lib/linter/attributeSchema.js.map +1 -0
  71. package/lib/linter/checkLocaleCircularDependency.d.ts +7 -0
  72. package/lib/linter/checkLocaleCircularDependency.js +42 -0
  73. package/lib/linter/checkLocaleCircularDependency.js.map +1 -0
  74. package/lib/linter/conditionSchema.d.ts +3 -0
  75. package/lib/linter/conditionSchema.js +283 -0
  76. package/lib/linter/conditionSchema.js.map +1 -0
  77. package/lib/linter/formatSchema.d.ts +325 -0
  78. package/lib/linter/formatSchema.js +165 -0
  79. package/lib/linter/formatSchema.js.map +1 -0
  80. package/lib/linter/icuStyleLint.d.ts +6 -0
  81. package/lib/linter/icuStyleLint.js +226 -0
  82. package/lib/linter/icuStyleLint.js.map +1 -0
  83. package/lib/linter/index.d.ts +34 -0
  84. package/lib/linter/index.js +557 -0
  85. package/lib/linter/index.js.map +1 -0
  86. package/lib/linter/localeSchema.d.ts +672 -0
  87. package/lib/linter/localeSchema.js +50 -0
  88. package/lib/linter/localeSchema.js.map +1 -0
  89. package/lib/linter/messageSchema.d.ts +35 -0
  90. package/lib/linter/messageSchema.js +115 -0
  91. package/lib/linter/messageSchema.js.map +1 -0
  92. package/lib/linter/printError.d.ts +8 -0
  93. package/lib/linter/printError.js +41 -0
  94. package/lib/linter/printError.js.map +1 -0
  95. package/lib/linter/schema.d.ts +33 -0
  96. package/lib/linter/schema.js +192 -0
  97. package/lib/linter/schema.js.map +1 -0
  98. package/lib/linter/segmentSchema.d.ts +8 -0
  99. package/lib/linter/segmentSchema.js +18 -0
  100. package/lib/linter/segmentSchema.js.map +1 -0
  101. package/lib/linter/targetSchema.d.ts +337 -0
  102. package/lib/linter/targetSchema.js +39 -0
  103. package/lib/linter/targetSchema.js.map +1 -0
  104. package/lib/linter/testSchema.d.ts +71 -0
  105. package/lib/linter/testSchema.js +165 -0
  106. package/lib/linter/testSchema.js.map +1 -0
  107. package/lib/linter/zodHelpers.d.ts +2 -0
  108. package/lib/linter/zodHelpers.js +15 -0
  109. package/lib/linter/zodHelpers.js.map +1 -0
  110. package/lib/list/index.d.ts +8 -0
  111. package/lib/list/index.js +524 -0
  112. package/lib/list/index.js.map +1 -0
  113. package/lib/matrix.d.ts +4 -0
  114. package/lib/matrix.js +66 -0
  115. package/lib/matrix.js.map +1 -0
  116. package/lib/promoter/index.d.ts +65 -0
  117. package/lib/promoter/index.js +1208 -0
  118. package/lib/promoter/index.js.map +1 -0
  119. package/lib/prune/index.d.ts +37 -0
  120. package/lib/prune/index.js +673 -0
  121. package/lib/prune/index.js.map +1 -0
  122. package/lib/sets.d.ts +10 -0
  123. package/lib/sets.js +120 -0
  124. package/lib/sets.js.map +1 -0
  125. package/lib/tester/cliFormat.d.ts +8 -0
  126. package/lib/tester/cliFormat.js +15 -0
  127. package/lib/tester/cliFormat.js.map +1 -0
  128. package/lib/tester/index.d.ts +35 -0
  129. package/lib/tester/index.js +713 -0
  130. package/lib/tester/index.js.map +1 -0
  131. package/lib/tester/matrix.d.ts +14 -0
  132. package/lib/tester/matrix.js +76 -0
  133. package/lib/tester/matrix.js.map +1 -0
  134. package/lib/tester/prettyDuration.d.ts +1 -0
  135. package/lib/tester/prettyDuration.js +30 -0
  136. package/lib/tester/prettyDuration.js.map +1 -0
  137. package/lib/tester/printTestResult.d.ts +2 -0
  138. package/lib/tester/printTestResult.js +32 -0
  139. package/lib/tester/printTestResult.js.map +1 -0
  140. package/lib/tester/types.d.ts +29 -0
  141. package/lib/tester/types.js +3 -0
  142. package/lib/tester/types.js.map +1 -0
  143. package/package.json +41 -13
  144. package/src/benchmark/index.spec.ts +375 -0
  145. package/src/benchmark/index.ts +433 -0
  146. package/src/builder/index.spec.ts +822 -0
  147. package/src/builder/index.ts +920 -0
  148. package/src/cli/index.spec.ts +54 -0
  149. package/src/cli/index.ts +150 -0
  150. package/src/config/index.spec.ts +70 -0
  151. package/src/config/index.ts +259 -0
  152. package/src/create/index.spec.ts +272 -0
  153. package/src/create/index.ts +295 -0
  154. package/src/datasource/filesystemAdapter.ts +313 -0
  155. package/src/datasource/index.ts +135 -0
  156. package/src/error.ts +33 -0
  157. package/src/evaluate/cli.spec.ts +368 -0
  158. package/src/evaluate/cli.ts +130 -0
  159. package/src/evaluate/index.ts +161 -0
  160. package/src/examples/coerceExampleIsoDates.spec.ts +81 -0
  161. package/src/examples/coerceExampleIsoDates.ts +98 -0
  162. package/src/examples/index.spec.ts +453 -0
  163. package/src/examples/index.ts +854 -0
  164. package/src/exporter/index.spec.ts +443 -0
  165. package/src/exporter/index.ts +643 -0
  166. package/src/find-duplicates/index.spec.ts +289 -0
  167. package/src/find-duplicates/index.ts +314 -0
  168. package/src/generate-code/index.ts +92 -0
  169. package/src/generate-code/typescript.spec.ts +241 -0
  170. package/src/generate-code/typescript.ts +284 -0
  171. package/src/importer/index.spec.ts +1101 -0
  172. package/src/importer/index.ts +1190 -0
  173. package/src/index.ts +18 -0
  174. package/src/info/index.ts +67 -0
  175. package/src/init/index.spec.ts +279 -0
  176. package/src/init/index.ts +292 -0
  177. package/src/lint/index.ts +1 -0
  178. package/src/linter/attributeSchema.ts +38 -0
  179. package/src/linter/checkLocaleCircularDependency.ts +51 -0
  180. package/src/linter/conditionSchema.ts +386 -0
  181. package/src/linter/formatSchema.ts +170 -0
  182. package/src/linter/icuStyleLint.ts +312 -0
  183. package/src/linter/index.spec.ts +824 -0
  184. package/src/linter/index.ts +460 -0
  185. package/src/linter/localeSchema.ts +70 -0
  186. package/src/linter/messageSchema.ts +152 -0
  187. package/src/linter/printError.ts +52 -0
  188. package/src/linter/schema.ts +230 -0
  189. package/src/linter/segmentSchema.ts +15 -0
  190. package/src/linter/targetSchema.ts +50 -0
  191. package/src/linter/testSchema.spec.ts +405 -0
  192. package/src/linter/testSchema.ts +239 -0
  193. package/src/linter/zodHelpers.ts +16 -0
  194. package/src/list/index.spec.ts +431 -0
  195. package/src/list/index.ts +463 -0
  196. package/src/matrix.ts +69 -0
  197. package/src/promoter/index.spec.ts +584 -0
  198. package/src/promoter/index.ts +1267 -0
  199. package/src/prune/index.spec.ts +418 -0
  200. package/src/prune/index.ts +693 -0
  201. package/src/sets.ts +74 -0
  202. package/src/tester/cliFormat.ts +11 -0
  203. package/src/tester/featurevisorIntegration.spec.ts +101 -0
  204. package/src/tester/index.spec.ts +577 -0
  205. package/src/tester/index.ts +679 -0
  206. package/src/tester/matrix.ts +106 -0
  207. package/src/tester/prettyDuration.ts +34 -0
  208. package/src/tester/printTestResult.ts +40 -0
  209. package/src/tester/types.ts +32 -0
  210. package/tsconfig.cjs.json +11 -0
  211. package/tsconfig.typecheck.json +4 -0
@@ -0,0 +1,854 @@
1
+ import { createMessagevisor, type MessageValues } from "@messagevisor/sdk";
2
+ import type {
3
+ Context,
4
+ DatafileContent,
5
+ FormatPresets,
6
+ Locale,
7
+ LocaleExample,
8
+ Message,
9
+ MessageExample,
10
+ } from "@messagevisor/types";
11
+
12
+ import type { Plugin } from "../cli";
13
+ import type { ProjectConfig } from "../config";
14
+ import type { Datasource } from "../datasource";
15
+ import { buildMessageDatafile, resolveFormats } from "../builder";
16
+ import { MessagevisorCLIError, printMessagevisorCLIError } from "../error";
17
+ import { applyCombinationToValue, getMatrixCombinations } from "../matrix";
18
+ import { getProjectSetExecutions } from "../sets";
19
+ import { colorize } from "../tester/cliFormat";
20
+ import { coerceExampleValuesIsoDates } from "./coerceExampleIsoDates";
21
+
22
+ export interface ResolvedLocaleExample {
23
+ set?: string;
24
+ locale: string;
25
+ sourceLocale: string;
26
+ exampleIndex: number;
27
+ matrixIndex?: number;
28
+ description?: string;
29
+ rawMessage?: string;
30
+ message?: string;
31
+ values?: Record<string, unknown>;
32
+ context?: Record<string, unknown>;
33
+ formats?: FormatPresets;
34
+ currency?: string;
35
+ timeZone?: string;
36
+ evaluatedTranslation: unknown;
37
+ evaluationInput?: ExampleEvaluationInput;
38
+ }
39
+
40
+ export interface ResolvedMessageExample {
41
+ set?: string;
42
+ message: string;
43
+ locale: string;
44
+ exampleIndex: number;
45
+ matrixIndex?: number;
46
+ description?: string;
47
+ values?: Record<string, unknown>;
48
+ context?: Record<string, unknown>;
49
+ formats?: FormatPresets;
50
+ currency?: string;
51
+ timeZone?: string;
52
+ evaluatedTranslation: unknown;
53
+ evaluationInput?: ExampleEvaluationInput;
54
+ }
55
+
56
+ export interface ExampleEvaluationInput {
57
+ datafile?: DatafileContent;
58
+ defaultFormats?: Record<string, FormatPresets>;
59
+ formats?: FormatPresets;
60
+ context?: Record<string, unknown>;
61
+ values?: Record<string, unknown>;
62
+ currency?: string;
63
+ timeZone?: string;
64
+ }
65
+
66
+ interface ExpandedLocaleExample extends Omit<
67
+ ResolvedLocaleExample,
68
+ "evaluatedTranslation" | "set"
69
+ > {
70
+ values?: MessageValues<any>;
71
+ }
72
+
73
+ interface ExpandedMessageExample extends Omit<
74
+ ResolvedMessageExample,
75
+ "evaluatedTranslation" | "set"
76
+ > {
77
+ values?: MessageValues<any>;
78
+ }
79
+
80
+ interface ExampleFilters {
81
+ exampleIndex?: number;
82
+ matrixIndex?: number;
83
+ descriptionPattern?: RegExp;
84
+ translationPattern?: RegExp;
85
+ }
86
+
87
+ export interface ExamplesOutput {
88
+ locales: ResolvedLocaleExample[];
89
+ messages: ResolvedMessageExample[];
90
+ }
91
+
92
+ interface ExampleSourceSelection {
93
+ includeLocales: boolean;
94
+ includeMessages: boolean;
95
+ }
96
+
97
+ export interface ResolveExamplesOptions {
98
+ set?: string;
99
+ locale?: string;
100
+ message?: string;
101
+ exampleIndex?: number | string;
102
+ matrixIndex?: number | string;
103
+ descriptionPattern?: string | RegExp;
104
+ translationPattern?: string | RegExp;
105
+ onlyMessages?: boolean;
106
+ onlyLocales?: boolean;
107
+ includeEvaluationInput?: boolean;
108
+ }
109
+
110
+ function parseOptionalPositiveInteger(name: string, value: unknown): number | undefined {
111
+ if (typeof value === "undefined") {
112
+ return undefined;
113
+ }
114
+
115
+ const parsedValue = Number(value);
116
+
117
+ if (!Number.isInteger(parsedValue) || parsedValue < 1) {
118
+ throw new MessagevisorCLIError(
119
+ `Invalid ${name}: expected an integer greater than or equal to 1.`,
120
+ );
121
+ }
122
+
123
+ return parsedValue;
124
+ }
125
+
126
+ function parseOptionalPattern(name: string, value: unknown): RegExp | undefined {
127
+ if (typeof value === "undefined") {
128
+ return undefined;
129
+ }
130
+
131
+ if (value instanceof RegExp) {
132
+ return value;
133
+ }
134
+
135
+ try {
136
+ return new RegExp(String(value), "i");
137
+ } catch (error: any) {
138
+ throw new MessagevisorCLIError(`Invalid ${name}: ${error.message}`);
139
+ }
140
+ }
141
+
142
+ function getExampleFilters(
143
+ parsed: Record<string, unknown> | ResolveExamplesOptions,
144
+ ): ExampleFilters {
145
+ return {
146
+ exampleIndex: parseOptionalPositiveInteger("--exampleIndex", parsed.exampleIndex),
147
+ matrixIndex: parseOptionalPositiveInteger("--matrixIndex", parsed.matrixIndex),
148
+ descriptionPattern: parseOptionalPattern("--descriptionPattern", parsed.descriptionPattern),
149
+ translationPattern: parseOptionalPattern("--translationPattern", parsed.translationPattern),
150
+ };
151
+ }
152
+
153
+ function getExampleSourceSelection(
154
+ parsed: Record<string, unknown> | ResolveExamplesOptions,
155
+ ): ExampleSourceSelection {
156
+ const onlyLocales = Boolean(parsed.onlyLocales);
157
+ const onlyMessages = Boolean(parsed.onlyMessages);
158
+
159
+ if (onlyLocales && onlyMessages) {
160
+ throw new MessagevisorCLIError("Pass either --onlyLocales or --onlyMessages, not both.");
161
+ }
162
+
163
+ return {
164
+ includeLocales: !onlyMessages,
165
+ includeMessages: !onlyLocales,
166
+ };
167
+ }
168
+
169
+ function matchesCommonFilters(
170
+ example: {
171
+ exampleIndex: number;
172
+ matrixIndex?: number;
173
+ description?: string;
174
+ evaluatedTranslation: unknown;
175
+ },
176
+ filters: ExampleFilters,
177
+ ) {
178
+ if (
179
+ typeof filters.exampleIndex === "number" &&
180
+ example.exampleIndex + 1 !== filters.exampleIndex
181
+ ) {
182
+ return false;
183
+ }
184
+
185
+ if (typeof filters.matrixIndex === "number") {
186
+ if (
187
+ typeof example.matrixIndex !== "number" ||
188
+ example.matrixIndex + 1 !== filters.matrixIndex
189
+ ) {
190
+ return false;
191
+ }
192
+ }
193
+
194
+ if (filters.descriptionPattern && !filters.descriptionPattern.test(example.description || "")) {
195
+ return false;
196
+ }
197
+
198
+ if (
199
+ filters.translationPattern &&
200
+ !filters.translationPattern.test(String(example.evaluatedTranslation))
201
+ ) {
202
+ return false;
203
+ }
204
+
205
+ return true;
206
+ }
207
+
208
+ async function readLocales(datasource: Datasource) {
209
+ const localeKeys = await datasource.listLocales();
210
+ const locales = Object.fromEntries(
211
+ await Promise.all(
212
+ localeKeys.map(
213
+ async (localeKey) =>
214
+ [localeKey, (await datasource.readLocale(localeKey)) as Locale] as const,
215
+ ),
216
+ ),
217
+ );
218
+
219
+ return { localeKeys, locales };
220
+ }
221
+
222
+ async function readMessages(datasource: Datasource) {
223
+ const messageKeys = await datasource.listMessages();
224
+ const messages = Object.fromEntries(
225
+ await Promise.all(
226
+ messageKeys.map(
227
+ async (messageKey) =>
228
+ [messageKey, (await datasource.readMessage(messageKey)) as Message] as const,
229
+ ),
230
+ ),
231
+ );
232
+
233
+ return { messageKeys, messages };
234
+ }
235
+
236
+ function resolveLocaleExampleChain(localeKey: string, locales: Record<string, Locale>): string[] {
237
+ const chain: string[] = [];
238
+ const seen = new Set<string>();
239
+ let currentKey: string | undefined = localeKey;
240
+
241
+ while (currentKey && !seen.has(currentKey)) {
242
+ seen.add(currentKey);
243
+ chain.unshift(currentKey);
244
+ currentKey = locales[currentKey]?.mergeExamplesFrom;
245
+ }
246
+
247
+ return chain;
248
+ }
249
+
250
+ function expandLocaleExample(
251
+ localeKey: string,
252
+ sourceLocale: string,
253
+ example: LocaleExample,
254
+ exampleIndex: number,
255
+ ): ExpandedLocaleExample[] {
256
+ if (!example.matrix) {
257
+ return [
258
+ {
259
+ locale: localeKey,
260
+ sourceLocale,
261
+ exampleIndex,
262
+ description: example.description,
263
+ rawMessage: example.rawMessage,
264
+ message: example.message,
265
+ values: example.values as MessageValues<any> | undefined,
266
+ context: example.context,
267
+ formats: example.formats,
268
+ currency: example.currency,
269
+ timeZone: example.timeZone,
270
+ },
271
+ ];
272
+ }
273
+
274
+ const combinations = getMatrixCombinations(example.matrix);
275
+
276
+ return combinations.map((combination, matrixIndex) => ({
277
+ locale: localeKey,
278
+ sourceLocale,
279
+ exampleIndex,
280
+ matrixIndex,
281
+ description: applyCombinationToValue(example.description, combination) as string | undefined,
282
+ rawMessage: applyCombinationToValue(example.rawMessage, combination) as string | undefined,
283
+ message: applyCombinationToValue(example.message, combination) as string | undefined,
284
+ values: applyCombinationToValue(example.values, combination) as MessageValues<any> | undefined,
285
+ context: applyCombinationToValue(example.context, combination) as
286
+ | Record<string, unknown>
287
+ | undefined,
288
+ formats: applyCombinationToValue(example.formats, combination) as FormatPresets | undefined,
289
+ currency: applyCombinationToValue(example.currency, combination) as string | undefined,
290
+ timeZone: applyCombinationToValue(example.timeZone, combination) as string | undefined,
291
+ }));
292
+ }
293
+
294
+ function resolveLocaleExamples(
295
+ localeKey: string,
296
+ locales: Record<string, Locale>,
297
+ ): ExpandedLocaleExample[] {
298
+ const chain = resolveLocaleExampleChain(localeKey, locales);
299
+ const result: ExpandedLocaleExample[] = [];
300
+ let exampleIndex = 0;
301
+
302
+ for (const sourceLocale of chain) {
303
+ const examples = locales[sourceLocale]?.examples || [];
304
+
305
+ for (const example of examples) {
306
+ result.push(...expandLocaleExample(localeKey, sourceLocale, example, exampleIndex));
307
+ exampleIndex += 1;
308
+ }
309
+ }
310
+
311
+ return result;
312
+ }
313
+
314
+ function expandMessageExample(
315
+ messageKey: string,
316
+ example: MessageExample,
317
+ exampleIndex: number,
318
+ ): ExpandedMessageExample[] {
319
+ if (!example.matrix) {
320
+ return [
321
+ {
322
+ message: messageKey,
323
+ locale: example.locale,
324
+ exampleIndex,
325
+ description: example.description,
326
+ values: example.values as MessageValues<any> | undefined,
327
+ context: example.context,
328
+ formats: example.formats,
329
+ currency: example.currency,
330
+ timeZone: example.timeZone,
331
+ },
332
+ ];
333
+ }
334
+
335
+ const combinations = getMatrixCombinations(example.matrix);
336
+
337
+ return combinations.map((combination, matrixIndex) => ({
338
+ message: messageKey,
339
+ locale: applyCombinationToValue(example.locale, combination) as string,
340
+ exampleIndex,
341
+ matrixIndex,
342
+ description: applyCombinationToValue(example.description, combination) as string | undefined,
343
+ values: applyCombinationToValue(example.values, combination) as MessageValues<any> | undefined,
344
+ context: applyCombinationToValue(example.context, combination) as
345
+ | Record<string, unknown>
346
+ | undefined,
347
+ formats: applyCombinationToValue(example.formats, combination) as FormatPresets | undefined,
348
+ currency: applyCombinationToValue(example.currency, combination) as string | undefined,
349
+ timeZone: applyCombinationToValue(example.timeZone, combination) as string | undefined,
350
+ }));
351
+ }
352
+
353
+ function resolveMessageExamples(messageKey: string, message: Message): ExpandedMessageExample[] {
354
+ const result: ExpandedMessageExample[] = [];
355
+ let exampleIndex = 0;
356
+
357
+ for (const example of message.examples || []) {
358
+ result.push(...expandMessageExample(messageKey, example, exampleIndex));
359
+ exampleIndex += 1;
360
+ }
361
+
362
+ return result;
363
+ }
364
+
365
+ async function evaluateLocaleExample(
366
+ projectConfig: ProjectConfig,
367
+ datasource: Datasource,
368
+ locales: Record<string, Locale>,
369
+ revision: string,
370
+ example: ExpandedLocaleExample,
371
+ includeEvaluationInput: boolean,
372
+ ): Promise<ResolvedLocaleExample> {
373
+ const evaluationValues = coerceExampleValuesIsoDates(
374
+ example.values as Record<string, unknown> | undefined,
375
+ ) as MessageValues<any> | undefined;
376
+
377
+ if (example.message) {
378
+ const datafile = await buildMessageDatafile(
379
+ projectConfig,
380
+ datasource,
381
+ example.message,
382
+ example.locale,
383
+ revision,
384
+ );
385
+ const messagevisor = createMessagevisor({
386
+ datafile,
387
+ locale: example.locale,
388
+ context: example.context as Context | undefined,
389
+ modules: projectConfig.modules || [],
390
+ logLevel: "warn",
391
+ });
392
+
393
+ return {
394
+ ...example,
395
+ evaluatedTranslation: messagevisor.translate(example.message, evaluationValues as any, {
396
+ context: example.context as Context | undefined,
397
+ formats: example.formats,
398
+ currency: example.currency,
399
+ timeZone: example.timeZone,
400
+ }),
401
+ evaluationInput: includeEvaluationInput
402
+ ? {
403
+ datafile,
404
+ formats: example.formats,
405
+ context: example.context,
406
+ values: evaluationValues as Record<string, unknown> | undefined,
407
+ currency: example.currency,
408
+ timeZone: example.timeZone,
409
+ }
410
+ : undefined,
411
+ };
412
+ }
413
+
414
+ const resolvedDefaultFormats = resolveFormats(example.locale, locales);
415
+ const defaultFormats = resolvedDefaultFormats
416
+ ? {
417
+ [example.locale]: resolvedDefaultFormats,
418
+ }
419
+ : undefined;
420
+ const messagevisor = createMessagevisor({
421
+ locale: example.locale,
422
+ context: example.context as Context | undefined,
423
+ defaultFormats,
424
+ modules: projectConfig.modules || [],
425
+ logLevel: "warn",
426
+ });
427
+
428
+ return {
429
+ ...example,
430
+ evaluatedTranslation: messagevisor.formatMessage(
431
+ example.rawMessage || "",
432
+ evaluationValues || {},
433
+ {
434
+ formats: example.formats,
435
+ currency: example.currency,
436
+ timeZone: example.timeZone,
437
+ },
438
+ ),
439
+ evaluationInput: includeEvaluationInput
440
+ ? {
441
+ defaultFormats,
442
+ formats: example.formats,
443
+ context: example.context,
444
+ values: evaluationValues as Record<string, unknown> | undefined,
445
+ currency: example.currency,
446
+ timeZone: example.timeZone,
447
+ }
448
+ : undefined,
449
+ };
450
+ }
451
+
452
+ async function evaluateMessageExample(
453
+ projectConfig: ProjectConfig,
454
+ datasource: Datasource,
455
+ revision: string,
456
+ example: ExpandedMessageExample,
457
+ includeEvaluationInput: boolean,
458
+ ): Promise<ResolvedMessageExample> {
459
+ const evaluationValues = coerceExampleValuesIsoDates(
460
+ example.values as Record<string, unknown> | undefined,
461
+ ) as MessageValues<any> | undefined;
462
+
463
+ const datafile = await buildMessageDatafile(
464
+ projectConfig,
465
+ datasource,
466
+ example.message,
467
+ example.locale,
468
+ revision,
469
+ );
470
+ const messagevisor = createMessagevisor({
471
+ datafile,
472
+ locale: example.locale,
473
+ context: example.context as Context | undefined,
474
+ modules: projectConfig.modules || [],
475
+ logLevel: "warn",
476
+ });
477
+
478
+ return {
479
+ ...example,
480
+ evaluatedTranslation: messagevisor.translate(example.message, evaluationValues as any, {
481
+ context: example.context as Context | undefined,
482
+ formats: example.formats,
483
+ currency: example.currency,
484
+ timeZone: example.timeZone,
485
+ }),
486
+ evaluationInput: includeEvaluationInput
487
+ ? {
488
+ datafile,
489
+ formats: example.formats,
490
+ context: example.context,
491
+ values: evaluationValues as Record<string, unknown> | undefined,
492
+ currency: example.currency,
493
+ timeZone: example.timeZone,
494
+ }
495
+ : undefined,
496
+ };
497
+ }
498
+
499
+ async function collectExamplesForExecution(
500
+ projectConfig: ProjectConfig,
501
+ datasource: Datasource,
502
+ sourceSelection: ExampleSourceSelection,
503
+ localeFilter?: string,
504
+ messageFilter?: string,
505
+ setKey?: string,
506
+ includeEvaluationInput = false,
507
+ ): Promise<ExamplesOutput> {
508
+ const { localeKeys, locales } = await readLocales(datasource);
509
+ const { messageKeys, messages } = await readMessages(datasource);
510
+
511
+ if (localeFilter && !localeKeys.includes(localeFilter)) {
512
+ throw new MessagevisorCLIError(
513
+ `Unknown locale "${localeFilter}". Available locales: ${localeKeys.join(", ") || "none"}.`,
514
+ );
515
+ }
516
+
517
+ if (messageFilter && !messageKeys.includes(messageFilter)) {
518
+ throw new MessagevisorCLIError(
519
+ `Unknown message "${messageFilter}". Available messages: ${messageKeys.join(", ") || "none"}.`,
520
+ );
521
+ }
522
+
523
+ const selectedLocaleKeys = localeFilter ? [localeFilter] : localeKeys;
524
+ const selectedMessageKeys = messageFilter ? [messageFilter] : messageKeys;
525
+ const revision = await datasource.readRevision();
526
+ const localeResults: ResolvedLocaleExample[] = [];
527
+ const messageResults: ResolvedMessageExample[] = [];
528
+
529
+ if (sourceSelection.includeLocales) {
530
+ for (const localeKey of selectedLocaleKeys) {
531
+ const examples = resolveLocaleExamples(localeKey, locales);
532
+
533
+ for (const example of examples) {
534
+ localeResults.push({
535
+ ...(await evaluateLocaleExample(
536
+ projectConfig,
537
+ datasource,
538
+ locales,
539
+ revision,
540
+ example,
541
+ includeEvaluationInput,
542
+ )),
543
+ set: setKey || undefined,
544
+ });
545
+ }
546
+ }
547
+ }
548
+
549
+ if (sourceSelection.includeMessages) {
550
+ for (const messageKey of selectedMessageKeys) {
551
+ const examples = resolveMessageExamples(messageKey, messages[messageKey]);
552
+
553
+ for (const example of examples) {
554
+ if (!localeKeys.includes(example.locale)) {
555
+ throw new Error(
556
+ `Unknown locale "${example.locale}" in examples for message "${messageKey}". Available locales: ${localeKeys.join(", ") || "none"}.`,
557
+ );
558
+ }
559
+
560
+ if (localeFilter && example.locale !== localeFilter) {
561
+ continue;
562
+ }
563
+
564
+ messageResults.push({
565
+ ...(await evaluateMessageExample(
566
+ projectConfig,
567
+ datasource,
568
+ revision,
569
+ example,
570
+ includeEvaluationInput,
571
+ )),
572
+ set: setKey || undefined,
573
+ });
574
+ }
575
+ }
576
+ }
577
+
578
+ return {
579
+ locales: localeResults,
580
+ messages: messageResults,
581
+ };
582
+ }
583
+
584
+ export async function resolveExamples(
585
+ projectConfig: ProjectConfig,
586
+ datasource: Datasource,
587
+ options: ResolveExamplesOptions = {},
588
+ ): Promise<ExamplesOutput> {
589
+ const filters = getExampleFilters(options);
590
+ const sourceSelection = getExampleSourceSelection(options);
591
+ const executions = await getProjectSetExecutions(projectConfig, datasource, options.set);
592
+ const results: ExamplesOutput = {
593
+ locales: [],
594
+ messages: [],
595
+ };
596
+
597
+ for (const execution of executions) {
598
+ const executionResults = await collectExamplesForExecution(
599
+ execution.projectConfig,
600
+ execution.datasource,
601
+ sourceSelection,
602
+ options.locale,
603
+ options.message,
604
+ execution.set,
605
+ options.includeEvaluationInput,
606
+ );
607
+
608
+ results.locales.push(
609
+ ...executionResults.locales.filter((example) => matchesCommonFilters(example, filters)),
610
+ );
611
+ results.messages.push(
612
+ ...executionResults.messages.filter((example) => matchesCommonFilters(example, filters)),
613
+ );
614
+ }
615
+
616
+ return results;
617
+ }
618
+
619
+ function printPlainLocaleExamples(examples: ResolvedLocaleExample[], hasSets: boolean) {
620
+ if (examples.length === 0) {
621
+ console.log(colorize("No locale examples found.", 33));
622
+ return;
623
+ }
624
+
625
+ let currentSet: string | undefined;
626
+ let currentLocale: string | undefined;
627
+
628
+ for (const example of examples) {
629
+ if (hasSets && example.set !== currentSet) {
630
+ currentSet = example.set;
631
+ currentLocale = undefined;
632
+ console.log(colorize(`Set "${currentSet}":`, 36));
633
+ console.log("");
634
+ }
635
+
636
+ if (example.locale !== currentLocale) {
637
+ currentLocale = example.locale;
638
+ console.log(colorize(`${hasSets ? " " : ""}Locale "${currentLocale}":`, 1));
639
+ }
640
+
641
+ const indent = hasSets ? " " : " ";
642
+ const titleParts = [`Example #${example.exampleIndex + 1}`];
643
+
644
+ if (typeof example.matrixIndex === "number") {
645
+ titleParts.push(`matrix #${example.matrixIndex + 1}`);
646
+ }
647
+
648
+ titleParts.push(`from ${example.sourceLocale}`);
649
+ console.log(colorize(`${indent}${titleParts.join(" · ")}`, 36));
650
+
651
+ if (example.description) {
652
+ console.log(colorize(`${indent} Description:`, 2));
653
+ console.log(`${indent} ${example.description}`);
654
+ }
655
+
656
+ if (example.message) {
657
+ console.log(colorize(`${indent} Message:`, 2));
658
+ console.log(`${indent} ${example.message}`);
659
+ }
660
+
661
+ if (example.rawMessage) {
662
+ console.log(colorize(`${indent} Raw message:`, 2));
663
+ console.log(`${indent} ${example.rawMessage}`);
664
+ }
665
+
666
+ if (typeof example.values !== "undefined") {
667
+ console.log(colorize(`${indent} Values:`, 2));
668
+ console.log(`${indent} ${JSON.stringify(example.values)}`);
669
+ }
670
+
671
+ if (typeof example.context !== "undefined") {
672
+ console.log(colorize(`${indent} Context:`, 2));
673
+ console.log(`${indent} ${JSON.stringify(example.context)}`);
674
+ }
675
+
676
+ if (typeof example.formats !== "undefined") {
677
+ console.log(colorize(`${indent} Formats:`, 2));
678
+ console.log(`${indent} ${JSON.stringify(example.formats)}`);
679
+ }
680
+
681
+ if (example.currency) {
682
+ console.log(colorize(`${indent} Currency:`, 2));
683
+ console.log(`${indent} ${example.currency}`);
684
+ }
685
+
686
+ if (example.timeZone) {
687
+ console.log(colorize(`${indent} Time zone:`, 2));
688
+ console.log(`${indent} ${example.timeZone}`);
689
+ }
690
+
691
+ console.log(colorize(`${indent} Evaluated translation:`, 2));
692
+ console.log(colorize(`${indent} ${JSON.stringify(example.evaluatedTranslation)}`, 32));
693
+ console.log("");
694
+ }
695
+ }
696
+
697
+ function printPlainMessageExamples(examples: ResolvedMessageExample[], hasSets: boolean) {
698
+ if (examples.length === 0) {
699
+ console.log(colorize("No message examples found.", 33));
700
+ return;
701
+ }
702
+
703
+ let currentSet: string | undefined;
704
+ let currentMessage: string | undefined;
705
+
706
+ for (const example of examples) {
707
+ if (hasSets && example.set !== currentSet) {
708
+ currentSet = example.set;
709
+ currentMessage = undefined;
710
+ console.log(colorize(`Set "${currentSet}":`, 36));
711
+ console.log("");
712
+ }
713
+
714
+ if (example.message !== currentMessage) {
715
+ currentMessage = example.message;
716
+ console.log(colorize(`${hasSets ? " " : ""}Message "${currentMessage}":`, 1));
717
+ }
718
+
719
+ const indent = hasSets ? " " : " ";
720
+ const titleParts = [`Example #${example.exampleIndex + 1}`];
721
+
722
+ if (typeof example.matrixIndex === "number") {
723
+ titleParts.push(`matrix #${example.matrixIndex + 1}`);
724
+ }
725
+
726
+ titleParts.push(`locale ${example.locale}`);
727
+ console.log(colorize(`${indent}${titleParts.join(" · ")}`, 36));
728
+
729
+ if (example.description) {
730
+ console.log(colorize(`${indent} Description:`, 2));
731
+ console.log(`${indent} ${example.description}`);
732
+ }
733
+
734
+ if (typeof example.values !== "undefined") {
735
+ console.log(colorize(`${indent} Values:`, 2));
736
+ console.log(`${indent} ${JSON.stringify(example.values)}`);
737
+ }
738
+
739
+ if (typeof example.context !== "undefined") {
740
+ console.log(colorize(`${indent} Context:`, 2));
741
+ console.log(`${indent} ${JSON.stringify(example.context)}`);
742
+ }
743
+
744
+ if (typeof example.formats !== "undefined") {
745
+ console.log(colorize(`${indent} Formats:`, 2));
746
+ console.log(`${indent} ${JSON.stringify(example.formats)}`);
747
+ }
748
+
749
+ if (example.currency) {
750
+ console.log(colorize(`${indent} Currency:`, 2));
751
+ console.log(`${indent} ${example.currency}`);
752
+ }
753
+
754
+ if (example.timeZone) {
755
+ console.log(colorize(`${indent} Time zone:`, 2));
756
+ console.log(`${indent} ${example.timeZone}`);
757
+ }
758
+
759
+ console.log(colorize(`${indent} Evaluated translation:`, 2));
760
+ console.log(colorize(`${indent} ${JSON.stringify(example.evaluatedTranslation)}`, 32));
761
+ console.log("");
762
+ }
763
+ }
764
+
765
+ function printPlainExamples(result: ExamplesOutput, hasSets: boolean) {
766
+ if (result.locales.length === 0 && result.messages.length === 0) {
767
+ console.log(colorize("No examples found.", 33));
768
+ return;
769
+ }
770
+
771
+ console.log("");
772
+ console.log(colorize("Messagevisor examples", 1));
773
+ console.log(` Locale examples: ${result.locales.length}`);
774
+ console.log(` Message examples: ${result.messages.length}`);
775
+ if (hasSets) {
776
+ console.log(
777
+ ` Sets: ${
778
+ new Set(
779
+ [...result.locales, ...result.messages].map((example) => example.set).filter(Boolean),
780
+ ).size
781
+ }`,
782
+ );
783
+ }
784
+ console.log("");
785
+
786
+ if (result.locales.length > 0) {
787
+ console.log(colorize("Locales", 1));
788
+ console.log("");
789
+ printPlainLocaleExamples(result.locales, hasSets);
790
+ }
791
+
792
+ if (result.messages.length > 0) {
793
+ console.log(colorize("Messages", 1));
794
+ console.log("");
795
+ printPlainMessageExamples(result.messages, hasSets);
796
+ }
797
+
798
+ const total = result.locales.length + result.messages.length;
799
+ console.log(colorize(`Found ${total} example${total === 1 ? "" : "s"}.`, 32));
800
+ console.log(colorize("Tip: use --json --pretty for structured output.", 2));
801
+ }
802
+
803
+ export const examplesPlugin: Plugin = {
804
+ command: "examples",
805
+ handler: async ({ projectConfig, datasource, parsed }) => {
806
+ try {
807
+ const results = await resolveExamples(projectConfig, datasource, {
808
+ set: parsed.set,
809
+ locale: parsed.locale,
810
+ exampleIndex: parsed.exampleIndex,
811
+ matrixIndex: parsed.matrixIndex,
812
+ descriptionPattern: parsed.descriptionPattern,
813
+ translationPattern: parsed.translationPattern,
814
+ onlyMessages: parsed.onlyMessages,
815
+ onlyLocales: parsed.onlyLocales,
816
+ includeEvaluationInput: parsed.includeEvaluationInput,
817
+ });
818
+
819
+ if (parsed.json) {
820
+ console.log(parsed.pretty ? JSON.stringify(results, null, 2) : JSON.stringify(results));
821
+ return;
822
+ }
823
+
824
+ printPlainExamples(results, projectConfig.sets);
825
+ } catch (error) {
826
+ if (printMessagevisorCLIError(error)) {
827
+ return false;
828
+ }
829
+
830
+ throw error;
831
+ }
832
+ },
833
+ examples: [
834
+ { command: "examples", description: "list all locale and message examples" },
835
+ { command: "examples --locale=en-US", description: "list examples for a specific locale" },
836
+ {
837
+ command: "examples --exampleIndex=2 --matrixIndex=3",
838
+ description: "list a specific expanded matrix example",
839
+ },
840
+ {
841
+ command: "examples --descriptionPattern=welcome --translationPattern=adult",
842
+ description: "filter examples by description or evaluated translation",
843
+ },
844
+ {
845
+ command: "examples --onlyMessages",
846
+ description: "list only message examples",
847
+ },
848
+ {
849
+ command: "examples --onlyLocales",
850
+ description: "list only locale examples",
851
+ },
852
+ { command: "examples --json --pretty", description: "print examples as JSON" },
853
+ ],
854
+ };