glotfox 0.0.0-stage → 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.
- package/README.md +128 -2
- package/dist/_shared/formats.d.ts +18 -0
- package/dist/check.d.ts +23 -0
- package/dist/chunk-AQOIF7AF.js +1286 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +137 -0
- package/dist/config.d.ts +25 -0
- package/dist/extract.d.ts +45 -0
- package/dist/glob.d.ts +5 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +24 -0
- package/dist/init.d.ts +8 -0
- package/dist/scan.d.ts +18 -0
- package/dist/sync.d.ts +37 -0
- package/dist/types.d.ts +21 -0
- package/package.json +31 -3
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
check,
|
|
4
|
+
extract,
|
|
5
|
+
generateTypes,
|
|
6
|
+
init,
|
|
7
|
+
loadConfig,
|
|
8
|
+
pull,
|
|
9
|
+
push
|
|
10
|
+
} from "./chunk-AQOIF7AF.js";
|
|
11
|
+
|
|
12
|
+
// src/cli.ts
|
|
13
|
+
import { parseArgs } from "node:util";
|
|
14
|
+
var HELP = `glotfox - developer-first i18n toolkit
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
glotfox init [--source <locale>] [--targets <a,b,...>] [--force]
|
|
18
|
+
glotfox extract [--dry-run] [--force]
|
|
19
|
+
glotfox check [--strict]
|
|
20
|
+
glotfox types [--out <file>] [--dry-run]
|
|
21
|
+
glotfox push [--dry-run] [--api-url <url>]
|
|
22
|
+
glotfox pull [--dry-run] [--api-url <url>]
|
|
23
|
+
glotfox --help | --version
|
|
24
|
+
|
|
25
|
+
Commands:
|
|
26
|
+
push Upload the source-locale keys (and descriptions) to GlotFox.
|
|
27
|
+
pull Download approved translations of the target locales into the
|
|
28
|
+
locale files. Authenticate with a project API key in GLOTFOX_API_KEY.
|
|
29
|
+
init Write glotfox.config.json in the current directory.
|
|
30
|
+
extract Scan source files for t('key'), <Trans i18nKey> and useTranslation,
|
|
31
|
+
and add new keys to the source-locale files. Never deletes anything;
|
|
32
|
+
keys no longer used in code are reported. Also regenerates the
|
|
33
|
+
type declarations when output.types is set.
|
|
34
|
+
check Read-only CI check: exit 1 if code has keys missing from the locale
|
|
35
|
+
files (run extract), the generated types are out of date, or a
|
|
36
|
+
translation is not valid ICU. Other QA findings are warnings.
|
|
37
|
+
types Generate a .d.ts with type-safe keys and ICU placeholder types.
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
--dry-run (extract, types) Report changes without writing files.
|
|
41
|
+
--out (types) Output file (default: output.types from the config).
|
|
42
|
+
--strict (check) Treat QA warnings as failures.
|
|
43
|
+
--force (init) Overwrite an existing config. (extract) Overwrite existing descriptions.
|
|
44
|
+
`;
|
|
45
|
+
function printTypes(r) {
|
|
46
|
+
for (const w of r.warnings) console.warn(`warning: ${w}`);
|
|
47
|
+
console.log(`${r.written ? "Wrote" : "Up to date:"} ${r.file} (${r.keys} keys)`);
|
|
48
|
+
}
|
|
49
|
+
async function main(argv) {
|
|
50
|
+
const { values, positionals } = parseArgs({
|
|
51
|
+
args: argv,
|
|
52
|
+
allowPositionals: true,
|
|
53
|
+
options: {
|
|
54
|
+
help: { type: "boolean", short: "h" },
|
|
55
|
+
version: { type: "boolean", short: "v" },
|
|
56
|
+
"dry-run": { type: "boolean" },
|
|
57
|
+
force: { type: "boolean" },
|
|
58
|
+
strict: { type: "boolean" },
|
|
59
|
+
source: { type: "string" },
|
|
60
|
+
targets: { type: "string" },
|
|
61
|
+
out: { type: "string" },
|
|
62
|
+
"api-url": { type: "string" }
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
const command = positionals[0];
|
|
66
|
+
if (values.version) {
|
|
67
|
+
console.log("0.1.0");
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
if (values.help || !command) {
|
|
71
|
+
console.log(HELP);
|
|
72
|
+
return command || values.help ? 0 : 1;
|
|
73
|
+
}
|
|
74
|
+
const cwd = process.cwd();
|
|
75
|
+
if (command === "init") {
|
|
76
|
+
const targetLocales = values.targets?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
77
|
+
console.log(`Created ${init({ cwd, sourceLocale: values.source, targetLocales, force: values.force })}`);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
if (command === "extract") {
|
|
81
|
+
const r = extract({ cwd, dryRun: values["dry-run"], force: values.force });
|
|
82
|
+
for (const w of r.warnings) console.warn(`warning: ${w}`);
|
|
83
|
+
console.log(`Scanned ${r.files} files, found ${r.found.length} keys.`);
|
|
84
|
+
console.log(`${values["dry-run"] ? "Would add" : "Added"} ${r.added.length} new key(s)${r.added.length ? ":" : "."}`);
|
|
85
|
+
for (const k of r.added) console.log(` + ${k}`);
|
|
86
|
+
if (r.unused.length) {
|
|
87
|
+
console.log(`${r.unused.length} unused key(s) (kept; not found in code):`);
|
|
88
|
+
for (const k of r.unused) console.log(` ? ${k}`);
|
|
89
|
+
}
|
|
90
|
+
if (!values["dry-run"] && loadConfig(cwd).config.output.types) printTypes(generateTypes({ cwd }));
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
if (command === "check") {
|
|
94
|
+
const r = check({ cwd, strict: values.strict });
|
|
95
|
+
for (const w of r.warnings) console.warn(`warning: ${w}`);
|
|
96
|
+
for (const f of r.issues) {
|
|
97
|
+
const line = `${f.locale} ${f.key}: ${f.issue.message} [${f.issue.code}]`;
|
|
98
|
+
if (f.issue.severity === "error" || values.strict) console.error(`${f.issue.severity === "error" ? "error" : "warning"}: ${line}`);
|
|
99
|
+
else console.warn(`warning: ${line}`);
|
|
100
|
+
}
|
|
101
|
+
if (r.missing.length) {
|
|
102
|
+
console.error(`${r.missing.length} key(s) used in code but missing from the source locale; run \`glotfox extract\`:`);
|
|
103
|
+
for (const k of r.missing) console.error(` + ${k}`);
|
|
104
|
+
}
|
|
105
|
+
if (r.typesStale) console.error(`${r.typesFile} is missing or out of date; run \`glotfox extract\` or \`glotfox types\`.`);
|
|
106
|
+
if (r.ok) console.log("OK: keys are extracted and types are up to date.");
|
|
107
|
+
return r.ok ? 0 : 1;
|
|
108
|
+
}
|
|
109
|
+
if (command === "types") {
|
|
110
|
+
printTypes(generateTypes({ cwd, out: values.out, dryRun: values["dry-run"] }));
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
if (command === "push") {
|
|
114
|
+
const r = await push({ cwd, apiUrl: values["api-url"], dryRun: values["dry-run"] });
|
|
115
|
+
console.log(
|
|
116
|
+
values["dry-run"] ? `Would push ${r.keys} key(s).` : `Pushed ${r.keys} key(s): ${r.created} new, ${r.updated} existing.`
|
|
117
|
+
);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
if (command === "pull") {
|
|
121
|
+
const r = await pull({ cwd, apiUrl: values["api-url"], dryRun: values["dry-run"] });
|
|
122
|
+
for (const [locale, n] of Object.entries(r.received)) console.log(`${locale}: ${n} approved translation(s)`);
|
|
123
|
+
console.log(`${values["dry-run"] ? "Would write" : "Wrote"} ${r.written.length} file(s)${r.written.length ? ":" : "."}`);
|
|
124
|
+
for (const f of r.written) console.log(` ${f}`);
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
console.error(`Unknown command "${command}"
|
|
128
|
+
`);
|
|
129
|
+
console.error(HELP);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
process.exitCode = await main(process.argv.slice(2));
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.error(`error: ${e.message}`);
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type FileFormat } from "./_shared/formats.js";
|
|
2
|
+
export declare const CONFIG_FILE = "glotfox.config.json";
|
|
3
|
+
export interface GlotfoxConfig {
|
|
4
|
+
version: number;
|
|
5
|
+
sourceLocale: string;
|
|
6
|
+
targetLocales: string[];
|
|
7
|
+
defaultNamespace: string;
|
|
8
|
+
include: string[];
|
|
9
|
+
exclude: string[];
|
|
10
|
+
output: {
|
|
11
|
+
dir: string;
|
|
12
|
+
pattern: string;
|
|
13
|
+
format: FileFormat;
|
|
14
|
+
emptyValues: "omit" | "empty";
|
|
15
|
+
types: string | null;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export declare const NAMESPACE_RE: RegExp;
|
|
19
|
+
/** Validate a parsed config object and apply defaults (translation file format spec, section 6). */
|
|
20
|
+
export declare function normalizeConfig(raw: unknown): GlotfoxConfig;
|
|
21
|
+
/** Find the nearest config in `cwd` or an ancestor. Returns the config and its directory (the project root). */
|
|
22
|
+
export declare function loadConfig(cwd: string): {
|
|
23
|
+
config: GlotfoxConfig;
|
|
24
|
+
root: string;
|
|
25
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type GlotfoxConfig } from "./config.js";
|
|
2
|
+
export interface ExtractOptions {
|
|
3
|
+
cwd: string;
|
|
4
|
+
dryRun?: boolean;
|
|
5
|
+
/** Overwrite descriptions that already exist in the meta files. */
|
|
6
|
+
force?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ExtractResult {
|
|
9
|
+
files: number;
|
|
10
|
+
/** "ns:key" of keys found in code. */
|
|
11
|
+
found: string[];
|
|
12
|
+
added: string[];
|
|
13
|
+
/** Keys present in the source locale but not found in code. Never deleted. */
|
|
14
|
+
unused: string[];
|
|
15
|
+
warnings: string[];
|
|
16
|
+
written: string[];
|
|
17
|
+
}
|
|
18
|
+
type Messages = Record<string, string>;
|
|
19
|
+
export declare function filePath(root: string, config: GlotfoxConfig, locale: string, namespace: string): string;
|
|
20
|
+
/** Descriptions always live in a JSON `<namespace>.meta.json`, whatever the locale file format. */
|
|
21
|
+
export declare function metaFilePath(root: string, config: GlotfoxConfig, locale: string, namespace: string): string;
|
|
22
|
+
export declare function readMessages(file: string, config: GlotfoxConfig): Messages;
|
|
23
|
+
/** Namespaces that already have a file for `locale`, derived from output.pattern. */
|
|
24
|
+
export declare function existingNamespaces(root: string, config: GlotfoxConfig, locale: string): string[];
|
|
25
|
+
/** A valid `t()` call found in code. */
|
|
26
|
+
export interface CodeUse {
|
|
27
|
+
/** `namespace:key` */
|
|
28
|
+
id: string;
|
|
29
|
+
namespace: string;
|
|
30
|
+
key: string;
|
|
31
|
+
/** Path relative to the project root, with `/` separators. */
|
|
32
|
+
file: string;
|
|
33
|
+
line: number;
|
|
34
|
+
defaultValue?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
maxLength?: number;
|
|
37
|
+
}
|
|
38
|
+
/** Every valid key use in the files matched by `include`/`exclude`, plus warnings for the invalid ones. */
|
|
39
|
+
export declare function scanProject(root: string, config: GlotfoxConfig): {
|
|
40
|
+
files: number;
|
|
41
|
+
uses: CodeUse[];
|
|
42
|
+
warnings: string[];
|
|
43
|
+
};
|
|
44
|
+
export declare function extract(opts: ExtractOptions): ExtractResult;
|
|
45
|
+
export {};
|
package/dist/glob.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Convert a glob (`*`, `**`, `?`, `{a,b}`) to a RegExp matching a "/"-separated relative path. */
|
|
2
|
+
export declare function globToRegExp(glob: string): RegExp;
|
|
3
|
+
/** List all files under `root` (relative, "/" separated), skipping node_modules and .git. */
|
|
4
|
+
export declare function walk(root: string, rel?: string): string[];
|
|
5
|
+
export declare function matchFiles(root: string, include: string[], exclude: string[]): string[];
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { extract, type ExtractOptions, type ExtractResult } from "./extract.js";
|
|
2
|
+
export { init, type InitOptions } from "./init.js";
|
|
3
|
+
export { loadConfig, normalizeConfig, type GlotfoxConfig } from "./config.js";
|
|
4
|
+
export { generateTypes, messageArgs, type TypesOptions, type TypesResult } from "./types.js";
|
|
5
|
+
export { push, pull, type SyncOptions, type PushResult, type PullResult } from "./sync.js";
|
|
6
|
+
export { check, type CheckResult } from "./check.js";
|
|
7
|
+
export { scanSource, type ExtractedKey } from "./scan.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
check,
|
|
3
|
+
extract,
|
|
4
|
+
generateTypes,
|
|
5
|
+
init,
|
|
6
|
+
loadConfig,
|
|
7
|
+
messageArgs,
|
|
8
|
+
normalizeConfig,
|
|
9
|
+
pull,
|
|
10
|
+
push,
|
|
11
|
+
scanSource
|
|
12
|
+
} from "./chunk-AQOIF7AF.js";
|
|
13
|
+
export {
|
|
14
|
+
check,
|
|
15
|
+
extract,
|
|
16
|
+
generateTypes,
|
|
17
|
+
init,
|
|
18
|
+
loadConfig,
|
|
19
|
+
messageArgs,
|
|
20
|
+
normalizeConfig,
|
|
21
|
+
pull,
|
|
22
|
+
push,
|
|
23
|
+
scanSource
|
|
24
|
+
};
|
package/dist/init.d.ts
ADDED
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Source scanner: finds t('key'), <Trans i18nKey=...> and useTranslation(ns) in JS/TS/JSX/TSX text. */
|
|
2
|
+
export interface ExtractedKey {
|
|
3
|
+
namespace: string | null;
|
|
4
|
+
key: string;
|
|
5
|
+
defaultValue?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
/** Character limit from `{ maxLength: 20 }`. */
|
|
8
|
+
maxLength?: number;
|
|
9
|
+
line: number;
|
|
10
|
+
}
|
|
11
|
+
export declare const KEY_RE: RegExp;
|
|
12
|
+
export declare const MAX_KEY_LENGTH = 128;
|
|
13
|
+
/** Read a '...', "..." or `...` literal (without ${}) starting at `i`. */
|
|
14
|
+
export declare function readString(src: string, i: number): {
|
|
15
|
+
value: string;
|
|
16
|
+
end: number;
|
|
17
|
+
} | null;
|
|
18
|
+
export declare function scanSource(src: string): ExtractedKey[];
|
package/dist/sync.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const DEFAULT_API_URL = "https://glotfox.app.dukauskas.lt";
|
|
2
|
+
export interface KeyReference {
|
|
3
|
+
file: string;
|
|
4
|
+
line: number;
|
|
5
|
+
}
|
|
6
|
+
export interface PushKey {
|
|
7
|
+
key: string;
|
|
8
|
+
value: string;
|
|
9
|
+
context?: string;
|
|
10
|
+
maxLength?: number;
|
|
11
|
+
references: KeyReference[];
|
|
12
|
+
}
|
|
13
|
+
export interface SyncOptions {
|
|
14
|
+
cwd: string;
|
|
15
|
+
/** Project API key (`gf_…`); defaults to $GLOTFOX_API_KEY. */
|
|
16
|
+
apiKey?: string;
|
|
17
|
+
/** Server base URL; defaults to $GLOTFOX_API_URL, then the hosted service. */
|
|
18
|
+
apiUrl?: string;
|
|
19
|
+
/** Injectable for tests. */
|
|
20
|
+
fetch?: typeof fetch;
|
|
21
|
+
dryRun?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface PushResult {
|
|
24
|
+
keys: number;
|
|
25
|
+
created: number;
|
|
26
|
+
updated: number;
|
|
27
|
+
}
|
|
28
|
+
export interface PullResult {
|
|
29
|
+
/** Files written (or that would be, with dryRun). */
|
|
30
|
+
written: string[];
|
|
31
|
+
/** Number of approved translations received per locale. */
|
|
32
|
+
received: Record<string, number>;
|
|
33
|
+
}
|
|
34
|
+
/** Upload the source-locale keys (and their descriptions) to the project. */
|
|
35
|
+
export declare function push(opts: SyncOptions): Promise<PushResult>;
|
|
36
|
+
/** Download approved translations of every target locale into the locale files. */
|
|
37
|
+
export declare function pull(opts: SyncOptions): Promise<PullResult>;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface TypesOptions {
|
|
2
|
+
cwd: string;
|
|
3
|
+
/** Override output.types (relative to the project root). */
|
|
4
|
+
out?: string;
|
|
5
|
+
dryRun?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface TypesResult {
|
|
8
|
+
file: string;
|
|
9
|
+
keys: number;
|
|
10
|
+
warnings: string[];
|
|
11
|
+
written: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Map of argument name -> set of TypeScript types, derived from an ICU message. Throws on malformed input. */
|
|
14
|
+
export declare function messageArgs(message: string): Map<string, Set<string>>;
|
|
15
|
+
/** Render the declaration file text for an `ns -> key -> message` catalog. */
|
|
16
|
+
export declare function renderTypes(catalog: Map<string, Record<string, string>>, defaultNamespace: string, warnings?: string[]): {
|
|
17
|
+
text: string;
|
|
18
|
+
keys: number;
|
|
19
|
+
};
|
|
20
|
+
/** Generate the TypeScript declaration file from the source-locale files. */
|
|
21
|
+
export declare function generateTypes(opts: TypesOptions): TypesResult;
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glotfox",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "GlotFox CLI: extract translation keys from React/Next.js and vanilla JS/TS projects",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"glotfox": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@glotfox/shared": "0.0.0",
|
|
24
|
+
"@types/node": "^22.10.0",
|
|
25
|
+
"esbuild": "^0.25.12",
|
|
26
|
+
"typescript": "^5.7.0",
|
|
27
|
+
"vitest": "^3.0.0"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "node build.mjs",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"test": "vitest run"
|
|
33
|
+
}
|
|
6
34
|
}
|