intor-cli 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -8
- package/package.json +4 -4
- package/src/build/build-schemas/build-schemas.ts +2 -2
- package/src/build/build-types/utils/render-infer-node.ts +1 -1
- package/src/build/index.ts +1 -1
- package/src/build/types.ts +4 -3
- package/src/cli/commands/check.ts +9 -26
- package/src/cli/commands/generate.ts +2 -13
- package/src/cli/commands/utils/normalize-reader-options.ts +46 -0
- package/src/cli/commands/validate.ts +50 -0
- package/src/cli/index.ts +2 -0
- package/src/core/collect-messages/collect-other-locale-messages.ts +27 -0
- package/src/core/collect-messages/index.ts +1 -0
- package/src/core/constants/generated.ts +5 -0
- package/src/core/constants/index.ts +4 -3
- package/src/core/extract-usages/index.ts +1 -1
- package/src/core/generated/ensure-and-write.ts +11 -0
- package/src/core/generated/index.ts +6 -0
- package/src/core/generated/read-schema.ts +33 -0
- package/src/core/generated/write-messages-snapshot.ts +27 -0
- package/src/core/generated/write-schema.ts +9 -0
- package/src/core/generated/write-types.ts +8 -0
- package/src/core/index.ts +36 -9
- package/src/core/infer-schema/index.ts +2 -1
- package/src/core/infer-schema/infer-schemas.ts +5 -1
- package/src/core/infer-schema/messages/infer-messages-schema.ts +1 -1
- package/src/core/infer-schema/replacements/infer-replacements-schema.ts +1 -1
- package/src/core/infer-schema/rich/infer-rich-schema.ts +1 -1
- package/src/core/infer-schema/utils/infer-object.ts +8 -6
- package/src/core/infer-schema/utils/should-skip-key.ts +15 -0
- package/src/core/infer-schema/utils/strip-internal-keys.ts +30 -0
- package/src/features/check/check.ts +7 -15
- package/src/{core → features/check}/diagnostics/collect.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/enforce-missing-replacements.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/enforce-missing-rich.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/key/empty.ts +1 -1
- package/src/{core → features/check}/diagnostics/rules/key/not-found.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/pre-key/not-found.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/replacement/missing.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/replacement/not-allowed.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/replacement/unused.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/rich/missing.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/rich/not-allowed.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/rich/unused.ts +1 -2
- package/src/{core → features/check}/diagnostics/types.ts +1 -1
- package/src/{core → features/check}/diagnostics/utils/get-schema-node-at-path.ts +1 -1
- package/src/{core → features/check}/diagnostics/utils/index-usages-by-key.ts +1 -1
- package/src/features/check/print-summary.ts +15 -15
- package/src/features/generate/generate.ts +16 -8
- package/src/features/generate/print-configs.ts +3 -3
- package/src/features/generate/print-overrides.ts +44 -26
- package/src/features/generate/print-summary.ts +21 -13
- package/src/features/index.ts +1 -0
- package/src/features/print.ts +43 -0
- package/src/features/validate/index.ts +1 -0
- package/src/features/validate/messages/index.ts +1 -0
- package/src/features/validate/messages/validate-messages-schema.ts +36 -0
- package/src/features/validate/print-summary.ts +65 -0
- package/src/features/validate/replacements/index.ts +1 -0
- package/src/features/validate/replacements/validate-replacements-schema.ts +51 -0
- package/src/features/validate/rich/index.ts +1 -0
- package/src/features/validate/rich/validate-rich-schema.ts +48 -0
- package/src/features/validate/validate-locale-messages.ts +38 -0
- package/src/features/validate/validate.ts +84 -0
- package/src/core/constants/generated-files.ts +0 -3
- package/src/core/read-generated-schema.ts +0 -42
- package/src/core/write-generated-files.ts +0 -51
- package/src/features/print-title.ts +0 -7
- /package/src/{core → features/check}/diagnostics/group.ts +0 -0
- /package/src/{core → features/check}/diagnostics/index.ts +0 -0
- /package/src/{core → features/check}/diagnostics/messages.ts +0 -0
- /package/src/{core → features/check}/diagnostics/rules/key/index.ts +0 -0
- /package/src/{core → features/check}/diagnostics/rules/pre-key/index.ts +0 -0
- /package/src/{core → features/check}/diagnostics/rules/replacement/index.ts +0 -0
- /package/src/{core → features/check}/diagnostics/rules/rich/index.ts +0 -0
- /package/src/{core → features/check}/diagnostics/utils/resolve-key-path.ts +0 -0
package/README.md
CHANGED
|
@@ -24,7 +24,13 @@ CLI tool for intor.
|
|
|
24
24
|
</table>
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## Overview
|
|
28
|
+
|
|
29
|
+
- **generate** — message schema & types
|
|
30
|
+
- **check** — usage analysis
|
|
31
|
+
- **validate** — locale completeness
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
28
34
|
|
|
29
35
|
#### generate
|
|
30
36
|
|
|
@@ -33,8 +39,8 @@ npx intor-cli generate
|
|
|
33
39
|
```
|
|
34
40
|
|
|
35
41
|
- Generates TypeScript types and schema artifacts
|
|
36
|
-
-
|
|
37
|
-
-
|
|
42
|
+
- Uses the default locale as the single source of truth
|
|
43
|
+
- Reports message override behavior during generation
|
|
38
44
|
|
|
39
45
|
#### check
|
|
40
46
|
|
|
@@ -42,13 +48,23 @@ npx intor-cli generate
|
|
|
42
48
|
npx intor-cli check
|
|
43
49
|
```
|
|
44
50
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
51
|
+
- Statistically analyzes translator usage in your codebase
|
|
52
|
+
- Detects incorrect keys, replacements, and rich tag usage
|
|
47
53
|
- Reports diagnostics with precise source locations
|
|
48
54
|
|
|
55
|
+
#### validate
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx intor-cli validate
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- Validates locale message completeness against schemas
|
|
62
|
+
- Checks missing keys, replacements, and rich tags
|
|
63
|
+
- Reports issues grouped by config and locale
|
|
64
|
+
|
|
49
65
|
## Design Guarantees
|
|
50
66
|
|
|
51
67
|
- Message types are inferred from the **_default locale_** only.
|
|
52
|
-
- All locales are
|
|
53
|
-
- Locale is treated as a runtime dimension, not a structural one.
|
|
54
|
-
- Generated types are intentionally conservative and do not
|
|
68
|
+
- All locales are expected to share the same message shape.
|
|
69
|
+
- Locale is treated strictly as a runtime dimension, not a structural one.
|
|
70
|
+
- Generated types are intentionally conservative and do not enforce locale completeness.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "intor-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "📟 CLI tool for intor",
|
|
5
5
|
"author": "Yiming Liao",
|
|
6
6
|
"homepage": "https://github.com/yiming-liao/intor-cli#readme",
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
"intor": "src/cli/index.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@intor/reader-md": "0.1.
|
|
43
|
-
"@intor/reader-yaml": "0.1.
|
|
42
|
+
"@intor/reader-md": "0.1.4",
|
|
43
|
+
"@intor/reader-yaml": "0.1.1",
|
|
44
44
|
"cac": "6.7.14",
|
|
45
45
|
"fast-glob": "3.3.3",
|
|
46
|
-
"intor": "2.3.
|
|
46
|
+
"intor": "2.3.26",
|
|
47
47
|
"logry": "2.1.6",
|
|
48
48
|
"ora": "9.0.0",
|
|
49
49
|
"picocolors": "1.1.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { BuildInput,
|
|
1
|
+
import type { BuildInput, Schema } from "../types";
|
|
2
2
|
|
|
3
|
-
export function buildSchemas(inputs: BuildInput[]):
|
|
3
|
+
export function buildSchemas(inputs: BuildInput[]): Schema {
|
|
4
4
|
return {
|
|
5
5
|
version: 1,
|
|
6
6
|
generatedAt: new Date().toISOString(),
|
package/src/build/index.ts
CHANGED
package/src/build/types.ts
CHANGED
|
@@ -7,12 +7,13 @@ export interface BuildInput {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
// Schema
|
|
10
|
-
export interface
|
|
10
|
+
export interface Schema {
|
|
11
11
|
version: number;
|
|
12
12
|
generatedAt: string;
|
|
13
|
-
configs:
|
|
13
|
+
configs: SchemaConfig[];
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
export interface SchemaConfig {
|
|
16
17
|
id: string;
|
|
17
18
|
locales: readonly string[];
|
|
18
19
|
schemas: InferredSchemas;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ReadGeneratedSchemaOptions } from "../../core";
|
|
2
1
|
import type { ExtractUsagesOptions } from "../../core/extract-usages/extract-usages";
|
|
3
2
|
import type { CAC } from "cac";
|
|
4
3
|
import { check } from "../../features";
|
|
@@ -13,20 +12,6 @@ export function registerCheckCommand(cli: CAC) {
|
|
|
13
12
|
// -----------------------------------------------------------------------
|
|
14
13
|
// Option
|
|
15
14
|
// -----------------------------------------------------------------------
|
|
16
|
-
// Read generated schema options
|
|
17
|
-
.option(
|
|
18
|
-
"--cwd <path>",
|
|
19
|
-
"Working directory for resolving intor config (default: process.cwd())",
|
|
20
|
-
)
|
|
21
|
-
.option(
|
|
22
|
-
"--file-name <name>",
|
|
23
|
-
"Generated schema file name (default: intor-generated.schema.json)",
|
|
24
|
-
)
|
|
25
|
-
.option(
|
|
26
|
-
"--out-dir <path>",
|
|
27
|
-
"Directory of generated schema output (default: .intor)",
|
|
28
|
-
)
|
|
29
|
-
// Extract usages options
|
|
30
15
|
.option(
|
|
31
16
|
"--tsconfig <path>",
|
|
32
17
|
"Path to tsconfig.json (default: tsconfig.json)",
|
|
@@ -36,15 +21,13 @@ export function registerCheckCommand(cli: CAC) {
|
|
|
36
21
|
// -----------------------------------------------------------------------
|
|
37
22
|
// Action
|
|
38
23
|
// -----------------------------------------------------------------------
|
|
39
|
-
.action(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
49
|
-
);
|
|
24
|
+
.action(async (options: ExtractUsagesOptions) => {
|
|
25
|
+
const { tsconfigPath, debug } = options;
|
|
26
|
+
try {
|
|
27
|
+
await check({ tsconfigPath, debug });
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(error);
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
50
33
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtraExt } from "../../core";
|
|
2
2
|
import type { CAC } from "cac";
|
|
3
3
|
import { generate } from "../../features";
|
|
4
|
+
import { normalizeReaderOptions } from "./utils/normalize-reader-options";
|
|
4
5
|
|
|
5
6
|
export function registerGenerateCommand(cli: CAC) {
|
|
6
7
|
cli
|
|
@@ -37,19 +38,7 @@ export function registerGenerateCommand(cli: CAC) {
|
|
|
37
38
|
debug?: boolean;
|
|
38
39
|
};
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
const exts = ext ? (Array.isArray(ext) ? ext : [ext]) : [];
|
|
42
|
-
|
|
43
|
-
// Parse customReaders: ["md=./my-md-reader.ts"] → { md: "./my-md-reader.ts" }
|
|
44
|
-
let customReaders: Record<string, string> | undefined;
|
|
45
|
-
if (reader && reader.length > 0) {
|
|
46
|
-
customReaders = {};
|
|
47
|
-
for (const item of reader) {
|
|
48
|
-
const [key, value] = item.split("=", 2);
|
|
49
|
-
if (!key || !value) continue;
|
|
50
|
-
customReaders[key] = value;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
41
|
+
const { exts, customReaders } = normalizeReaderOptions({ ext, reader });
|
|
53
42
|
|
|
54
43
|
try {
|
|
55
44
|
await generate({ exts, customReaders, debug });
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ExtraExt } from "../../../core";
|
|
2
|
+
|
|
3
|
+
export interface NormalizedReaderOptions {
|
|
4
|
+
exts: ExtraExt[];
|
|
5
|
+
customReaders?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RawReaderOptions {
|
|
9
|
+
ext?: string | string[];
|
|
10
|
+
reader?: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Normalize CLI reader-related options:
|
|
15
|
+
* - ext: string | string[] → string[]
|
|
16
|
+
* - reader: ["md=./reader.ts"] → { md: "./reader.ts" }
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeReaderOptions(
|
|
19
|
+
options: RawReaderOptions,
|
|
20
|
+
): NormalizedReaderOptions {
|
|
21
|
+
// Normalize exts
|
|
22
|
+
const exts = options.ext
|
|
23
|
+
? Array.isArray(options.ext)
|
|
24
|
+
? options.ext
|
|
25
|
+
: [options.ext]
|
|
26
|
+
: [];
|
|
27
|
+
|
|
28
|
+
// Normalize custom readers
|
|
29
|
+
let customReaders: Record<string, string> | undefined;
|
|
30
|
+
|
|
31
|
+
if (options.reader && options.reader.length > 0) {
|
|
32
|
+
customReaders = {};
|
|
33
|
+
|
|
34
|
+
for (const item of options.reader) {
|
|
35
|
+
const [key, value] = item.split("=", 2);
|
|
36
|
+
if (!key || !value) continue;
|
|
37
|
+
|
|
38
|
+
customReaders[key] = value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
exts: exts as ExtraExt[],
|
|
44
|
+
customReaders,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ExtraExt } from "../../core";
|
|
2
|
+
import type { CAC } from "cac";
|
|
3
|
+
import { validate } from "../../features";
|
|
4
|
+
import { normalizeReaderOptions } from "./utils/normalize-reader-options";
|
|
5
|
+
|
|
6
|
+
export function registerValidateCommand(cli: CAC) {
|
|
7
|
+
cli
|
|
8
|
+
// -----------------------------------------------------------------------
|
|
9
|
+
// Command
|
|
10
|
+
// -----------------------------------------------------------------------
|
|
11
|
+
.command("validate", "Validate intor locale translations")
|
|
12
|
+
|
|
13
|
+
// -----------------------------------------------------------------------
|
|
14
|
+
// Option
|
|
15
|
+
// -----------------------------------------------------------------------
|
|
16
|
+
.option(
|
|
17
|
+
"--ext <ext>",
|
|
18
|
+
"Enable extra messages file extension (repeatable)",
|
|
19
|
+
{ default: [] },
|
|
20
|
+
)
|
|
21
|
+
.option(
|
|
22
|
+
"--reader <mapping>",
|
|
23
|
+
"Custom reader mapping in the form <ext=path> (repeatable)",
|
|
24
|
+
{ default: [] },
|
|
25
|
+
)
|
|
26
|
+
.option(
|
|
27
|
+
"--debug",
|
|
28
|
+
"Print debug information during config discovery and generation",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// -----------------------------------------------------------------------
|
|
32
|
+
// Action
|
|
33
|
+
// -----------------------------------------------------------------------
|
|
34
|
+
.action(async (options) => {
|
|
35
|
+
const { ext, reader, debug } = options as {
|
|
36
|
+
ext?: Array<ExtraExt>;
|
|
37
|
+
reader?: string[];
|
|
38
|
+
debug?: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const { exts, customReaders } = normalizeReaderOptions({ ext, reader });
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await validate({ exts, customReaders, debug });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(error);
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { cac } from "cac";
|
|
4
4
|
import { registerCheckCommand } from "./commands/check";
|
|
5
5
|
import { registerGenerateCommand } from "./commands/generate";
|
|
6
|
+
import { registerValidateCommand } from "./commands/validate";
|
|
6
7
|
|
|
7
8
|
const cli = cac("intor");
|
|
8
9
|
|
|
@@ -11,6 +12,7 @@ const cli = cac("intor");
|
|
|
11
12
|
// ---------------------------------------------------------------------
|
|
12
13
|
registerGenerateCommand(cli);
|
|
13
14
|
registerCheckCommand(cli);
|
|
15
|
+
registerValidateCommand(cli);
|
|
14
16
|
|
|
15
17
|
// ---------------------------------------------------------------------
|
|
16
18
|
// Global options / help
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ExtraExt } from "../constants";
|
|
2
|
+
import type { IntorResolvedConfig, MessageObject } from "intor";
|
|
3
|
+
import { collectRuntimeMessages } from "./collect-runtime-messages";
|
|
4
|
+
|
|
5
|
+
export async function collectOtherLocaleMessages(
|
|
6
|
+
config: IntorResolvedConfig,
|
|
7
|
+
exts: Array<ExtraExt> = [],
|
|
8
|
+
customReaders?: Record<string, string>,
|
|
9
|
+
): Promise<Record<string, MessageObject>> {
|
|
10
|
+
const { supportedLocales, defaultLocale } = config;
|
|
11
|
+
|
|
12
|
+
const result: Record<string, MessageObject> = {};
|
|
13
|
+
|
|
14
|
+
for (const locale of supportedLocales) {
|
|
15
|
+
if (locale === defaultLocale) continue;
|
|
16
|
+
|
|
17
|
+
const { messages } = await collectRuntimeMessages(
|
|
18
|
+
config,
|
|
19
|
+
locale,
|
|
20
|
+
exts,
|
|
21
|
+
customReaders,
|
|
22
|
+
);
|
|
23
|
+
result[locale] = messages[locale];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export {
|
|
2
2
|
DEFAULT_OUT_DIR,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
DEFAULT_TYPES_FILE_NAME,
|
|
4
|
+
DEFAULT_SCHEMA_FILE_NAME,
|
|
5
|
+
DEFAULT_MESSAGES_SNAPSHOT_DIR,
|
|
6
|
+
} from "./generated";
|
|
6
7
|
|
|
7
8
|
export { EXTRA_EXTS, type ExtraExt } from "./extra-exts";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function ensureAndWrite(
|
|
5
|
+
filePath: string,
|
|
6
|
+
content: string,
|
|
7
|
+
): Promise<string> {
|
|
8
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
9
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
10
|
+
return filePath;
|
|
11
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Schema } from "../../build";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME } from "../constants";
|
|
5
|
+
|
|
6
|
+
export async function readSchema(): Promise<Schema> {
|
|
7
|
+
const filePath = path.join(DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME);
|
|
8
|
+
|
|
9
|
+
let raw: string;
|
|
10
|
+
|
|
11
|
+
// ------------------------------------------------------------------
|
|
12
|
+
// Read file
|
|
13
|
+
// ------------------------------------------------------------------
|
|
14
|
+
try {
|
|
15
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
16
|
+
} catch {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Failed to read Intor schema file at "${filePath}".\n` +
|
|
19
|
+
`Have you run "intor generate"?`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ------------------------------------------------------------------
|
|
24
|
+
// Parse JSON
|
|
25
|
+
// ------------------------------------------------------------------
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(raw) as Schema;
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Invalid JSON format in Intor schema file at "${filePath}".`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/* eslint-disable unicorn/no-array-sort */
|
|
2
|
+
import type { MessageObject } from "intor";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { DEFAULT_OUT_DIR, DEFAULT_MESSAGES_SNAPSHOT_DIR } from "../constants";
|
|
5
|
+
import { ensureAndWrite } from "./ensure-and-write";
|
|
6
|
+
|
|
7
|
+
export async function writeMessagesSnapshot(
|
|
8
|
+
localeMessages: Record<string, MessageObject>,
|
|
9
|
+
): Promise<string[]> {
|
|
10
|
+
const filePaths: string[] = [];
|
|
11
|
+
|
|
12
|
+
for (const locale of Object.keys(localeMessages).sort()) {
|
|
13
|
+
const messages = localeMessages[locale];
|
|
14
|
+
|
|
15
|
+
const filePath = path.join(
|
|
16
|
+
DEFAULT_OUT_DIR,
|
|
17
|
+
DEFAULT_MESSAGES_SNAPSHOT_DIR,
|
|
18
|
+
`${locale}.json`,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
await ensureAndWrite(filePath, JSON.stringify(messages, null, 2));
|
|
22
|
+
|
|
23
|
+
filePaths.push(filePath);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return filePaths;
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Schema } from "../../build";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME } from "../constants";
|
|
4
|
+
import { ensureAndWrite } from "./ensure-and-write";
|
|
5
|
+
|
|
6
|
+
export async function writeSchema(schema: Schema): Promise<string> {
|
|
7
|
+
const filePath = path.join(DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME);
|
|
8
|
+
return await ensureAndWrite(filePath, JSON.stringify(schema, null, 2));
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { DEFAULT_OUT_DIR, DEFAULT_TYPES_FILE_NAME } from "../constants";
|
|
3
|
+
import { ensureAndWrite } from "./ensure-and-write";
|
|
4
|
+
|
|
5
|
+
export async function writeTypes(types: string): Promise<string> {
|
|
6
|
+
const filePath = path.join(DEFAULT_OUT_DIR, DEFAULT_TYPES_FILE_NAME);
|
|
7
|
+
return await ensureAndWrite(filePath, types);
|
|
8
|
+
}
|
package/src/core/index.ts
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
|
+
// discover-configs
|
|
1
2
|
export { discoverConfigs } from "./discover-configs";
|
|
2
|
-
export { collectRuntimeMessages } from "./collect-messages";
|
|
3
|
-
export { inferSchemas, type InferredSchemas } from "./infer-schema";
|
|
4
|
-
export { extractUsages } from "./extract-usages";
|
|
5
|
-
export { collectDiagnostics, groupDiagnostics } from "./diagnostics";
|
|
6
3
|
|
|
7
|
-
|
|
4
|
+
// collect-messages
|
|
8
5
|
export {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "./
|
|
6
|
+
collectRuntimeMessages,
|
|
7
|
+
collectOtherLocaleMessages,
|
|
8
|
+
} from "./collect-messages";
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
// infer-schema
|
|
11
|
+
export {
|
|
12
|
+
inferSchemas,
|
|
13
|
+
type InferNode,
|
|
14
|
+
type InferredSchemas,
|
|
15
|
+
extractInterpolationNames,
|
|
16
|
+
} from "./infer-schema";
|
|
17
|
+
|
|
18
|
+
// extract-usages
|
|
19
|
+
export {
|
|
20
|
+
extractUsages,
|
|
21
|
+
type ExtractUsagesOptions,
|
|
22
|
+
type ExtractedUsages,
|
|
23
|
+
type TranslatorFactory,
|
|
24
|
+
type TranslatorMethod,
|
|
25
|
+
type ReplacementUsage,
|
|
26
|
+
type PreKeyUsage,
|
|
27
|
+
type KeyUsage,
|
|
28
|
+
type RichUsage,
|
|
29
|
+
} from "./extract-usages";
|
|
30
|
+
|
|
31
|
+
// generated
|
|
32
|
+
export {
|
|
33
|
+
writeTypes,
|
|
34
|
+
writeSchema,
|
|
35
|
+
readSchema,
|
|
36
|
+
writeMessagesSnapshot,
|
|
37
|
+
} from "./generated";
|
|
38
|
+
|
|
39
|
+
// constants
|
|
40
|
+
export { DEFAULT_OUT_DIR, EXTRA_EXTS, type ExtraExt } from "./constants";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { inferSchemas } from "./infer-schemas";
|
|
2
2
|
export type { InferNode, InferredSchemas } from "./types";
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
export { extractInterpolationNames } from "./replacements/extract-interpolation-names";
|
|
@@ -3,14 +3,18 @@ import type { MessageObject } from "intor";
|
|
|
3
3
|
import { inferMessagesSchema } from "./messages";
|
|
4
4
|
import { inferReplacementsSchema } from "./replacements";
|
|
5
5
|
import { inferRichSchema } from "./rich";
|
|
6
|
+
import { stripInternalKeys } from "./utils/strip-internal-keys";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Infer all semantic schemas from messages.
|
|
9
10
|
*/
|
|
10
11
|
export function inferSchemas(messages: MessageObject): InferredSchemas {
|
|
11
|
-
|
|
12
|
+
const schemas = {
|
|
12
13
|
messagesSchema: inferMessagesSchema(messages),
|
|
13
14
|
replacementsSchema: inferReplacementsSchema(messages),
|
|
14
15
|
richSchema: inferRichSchema(messages),
|
|
15
16
|
};
|
|
17
|
+
|
|
18
|
+
stripInternalKeys(schemas);
|
|
19
|
+
return schemas;
|
|
16
20
|
}
|
|
@@ -49,7 +49,7 @@ function inferValue(value: MessageValue): InferNode {
|
|
|
49
49
|
// Object values
|
|
50
50
|
// ----------------------------------------------------------------------
|
|
51
51
|
if (isMessageObject(value)) {
|
|
52
|
-
const result = inferObject(value, inferValue);
|
|
52
|
+
const result = inferObject(value, inferValue, "messages");
|
|
53
53
|
|
|
54
54
|
// empty object → fallback record
|
|
55
55
|
if (result.kind === "none") {
|
|
@@ -55,7 +55,7 @@ function inferValue(value: MessageValue): InferNode {
|
|
|
55
55
|
// Object values (delegate aggregation & pruning)
|
|
56
56
|
// ----------------------------------------------------------------------
|
|
57
57
|
if (isMessageObject(value)) {
|
|
58
|
-
return inferObject(value, inferValue);
|
|
58
|
+
return inferObject(value, inferValue, "replacements");
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// Fallback
|
|
@@ -58,7 +58,7 @@ function inferValue(value: MessageValue): InferNode {
|
|
|
58
58
|
// Object values (delegate aggregation & pruning)
|
|
59
59
|
// ----------------------------------------------------------------------
|
|
60
60
|
if (isMessageObject(value)) {
|
|
61
|
-
return inferObject(value, inferValue);
|
|
61
|
+
return inferObject(value, inferValue, "rich");
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Fallback
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { InferNode } from "../types";
|
|
2
2
|
import type { MessageObject, MessageValue } from "intor";
|
|
3
|
+
import { shouldSkipKey } from "./should-skip-key";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Infer an object-like semantic node by aggregating inferred children.
|
|
@@ -8,13 +9,17 @@ import type { MessageObject, MessageValue } from "intor";
|
|
|
8
9
|
* - Prunes branches without semantic meaning
|
|
9
10
|
* - Returns `none` if no children remain
|
|
10
11
|
*/
|
|
12
|
+
|
|
11
13
|
export function inferObject(
|
|
12
14
|
value: MessageObject,
|
|
13
15
|
inferChild: (value: MessageValue) => InferNode,
|
|
16
|
+
mode: "messages" | "replacements" | "rich",
|
|
14
17
|
): InferNode {
|
|
15
18
|
const properties: Record<string, InferNode> = {};
|
|
16
19
|
|
|
17
20
|
for (const [key, val] of Object.entries(value)) {
|
|
21
|
+
if (shouldSkipKey(key, mode)) continue;
|
|
22
|
+
|
|
18
23
|
const child = inferChild(val);
|
|
19
24
|
|
|
20
25
|
// Skip branches without semantic meaning
|
|
@@ -23,10 +28,7 @@ export function inferObject(
|
|
|
23
28
|
properties[key] = child;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return { kind: "object", properties };
|
|
31
|
+
return Object.keys(properties).length === 0
|
|
32
|
+
? { kind: "none" }
|
|
33
|
+
: { kind: "object", properties };
|
|
32
34
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determine whether a message object key should be ignored during schema inference.
|
|
3
|
+
*
|
|
4
|
+
* - Internal Intor metadata keys (`__intor_*`) are never part of semantic schemas
|
|
5
|
+
* - Markdown payload (`content`) is only meaningful in Messages schema,
|
|
6
|
+
* and must be excluded from Replacements / Rich inference
|
|
7
|
+
*/
|
|
8
|
+
export function shouldSkipKey(
|
|
9
|
+
key: string,
|
|
10
|
+
mode: "messages" | "replacements" | "rich",
|
|
11
|
+
) {
|
|
12
|
+
if (key.startsWith("__intor_")) return true;
|
|
13
|
+
if (mode !== "messages" && key === "content") return true;
|
|
14
|
+
return false;
|
|
15
|
+
}
|