@chipmobilesdk/rn-i18n-tooling 0.0.0-stage → 0.1.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,77 @@
1
+ # @chipmobilesdk/rn-i18n-tooling
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - `chip-i18n` runs on Linux and macOS again. ([#27](https://github.com/operationd8979/chip-mobile-sdk/pull/27))
8
+
9
+ Every line of `bin/chip-i18n.mjs` in 0.1.0 ends in CRLF, the shebang included, so the
10
+ kernel reads the interpreter name as `node\r` and the command dies before it starts:
11
+
12
+ ```
13
+ $ npx chip-i18n --help
14
+ /usr/bin/env: 'node\r': No such file or directory
15
+ ```
16
+
17
+ Only away from Windows. 0.1.0 had to be published by hand — npm's trusted publishing
18
+ cannot create a package that does not exist yet — and `npm pack` ships the working tree
19
+ as it stands, which on a Windows checkout (`core.autocrlf`) had every line rewritten. The
20
+ package's own smoke test installs the tarball and runs the binary, but it passes on
21
+ Windows, where CRLF is fine, and in CI, where the checkout is LF: the broken artifact
22
+ exists only in between.
23
+
24
+ `.gitattributes` now pins `packages/*/bin/**` to LF so no checkout can rewrite it, and
25
+ `npm run check:manifests` fails on a `bin` target that contains a carriage return or is
26
+ missing its shebang. Nothing else in the package changed.
27
+
28
+ ## 0.1.0
29
+
30
+ ### Minor Changes
31
+
32
+ - The extraction CLI is a package of its own, and it runs from npm. ([#21](https://github.com/operationd8979/chip-mobile-sdk/pull/21))
33
+
34
+ `@chipmobilesdk/rn-i18n-tooling` is new: it builds to JavaScript, declares the Node it
35
+ needs (`>=22.13.0`) and exposes a `chip-i18n` binary.
36
+
37
+ ```sh
38
+ npm i -D @chipmobilesdk/rn-i18n-tooling
39
+ npx chip-i18n sync # was: node --experimental-strip-types \
40
+ # node_modules/@chipmobilesdk/rn-i18n/src/tooling/cli.ts
41
+ ```
42
+
43
+ The tooling used to ship inside the runtime tarball as TypeScript source behind a
44
+ `./tooling` subpath, so the command the README documented worked in this monorepo and
45
+ nowhere else — a consumer had no `bin`, and `node` could not execute the `.ts`
46
+ (I18N-01, DEP-02). A test now installs the package into a throwaway app's
47
+ `node_modules` from exactly what `files` lists and runs the binary there, so "works
48
+ when published" is checked rather than asserted.
49
+
50
+ `@chipmobilesdk/rn-i18n` therefore has one entrypoint and no subpaths, and its tarball
51
+ no longer carries `src/tooling`. **Migration:** an app that imported
52
+ `@chipmobilesdk/rn-i18n/tooling` for the programmatic API installs the new package and
53
+ imports `@chipmobilesdk/rn-i18n-tooling` instead; the members are unchanged
54
+ (`runSync`, `scanSource`, `syncLocaleFiles`, `buildReport`, `renderReport`,
55
+ `generateKeysFile`, `renderKeysModule`, `validateSyncConfig`, `listSourceFiles`,
56
+ `globToRegExp`). An app that only ran the CLI replaces its script with `chip-i18n sync`.
57
+
58
+ Three things the move fixed while the code was in hand:
59
+
60
+ - **The scan starts where the glob does.** `listSourceFiles` walked the project root and
61
+ threw away almost everything it read; it now walks each glob's own base directory
62
+ (`src/screens/**/*.tsx` → `src/screens`), one walk per distinct base.
63
+ - **A match offset becomes a line number by binary search** over the file's line
64
+ starts, computed once. Counting newlines per match made a file with many keys
65
+ quadratic in its own length, and the ambiguous-usage snippet re-split the whole file
66
+ for every hit.
67
+ - **A sync with nothing to do writes nothing.** Each locale file and the generated keys
68
+ file are compared with what would be written — with line endings normalised, so a
69
+ Windows checkout's CRLF is not "a change" — and left alone when they match. Rewriting
70
+ identical bytes churned every mtime, which woke watchers and showed work in
71
+ `git status` that had not happened. The report says `Locale files: unchanged, nothing
72
+ written`.
73
+
74
+ `chip-i18n` also takes a command rather than only flags: `sync` is the one command,
75
+ `--help` prints usage, an unknown command exits 2, and a bare invocation still means
76
+ `sync` for a script that predates the name.
77
+
package/README.md CHANGED
@@ -1,3 +1,65 @@
1
- # Temporary Holding Version
1
+ # @chipmobilesdk/rn-i18n-tooling
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The `chip-i18n` CLI: it scans a React Native app's source for translation keys and
4
+ synchronises the app's locale JSON files against what it finds.
5
+
6
+ - **Node, not React Native.** Nothing here runs on a device, and the package is a
7
+ `devDependency` of the app. The runtime library is
8
+ [`@chipmobilesdk/rn-i18n`](../rn-i18n/README.md).
9
+ - **Ships JavaScript.** The tarball carries `lib/` built from TypeScript plus the
10
+ `bin/chip-i18n.mjs` entry. This package exists because the tooling used to ship as
11
+ `.ts` source inside the runtime tarball, where the documented command — `node
12
+ --experimental-strip-types node_modules/@chipmobilesdk/rn-i18n/src/tooling/cli.ts` —
13
+ worked in this monorepo and nowhere else (I18N-01, DEP-02).
14
+ - **Public package.** Published under the `@chipmobilesdk` npm organization. The
15
+ license is `UNLICENSED`: the code is readable on npm, but
16
+ no open-source license is granted.
17
+ - **Version**: see [CHANGELOG](CHANGELOG.md)
18
+
19
+ ## Install
20
+
21
+ ```sh
22
+ npm i -D @chipmobilesdk/rn-i18n-tooling
23
+ ```
24
+
25
+ Node `>=22.13.0`.
26
+
27
+ ## Use
28
+
29
+ ```sh
30
+ npx chip-i18n sync # i18n-sync.config.json
31
+ npx chip-i18n sync --config other.config.json
32
+ npx chip-i18n --help
33
+ ```
34
+
35
+ The configuration file, the key patterns, the runtime-only key policy and the generated
36
+ typed-key contract are documented in the runtime package's README, under
37
+ `## Extraction & synchronization CLI` — the CLI reads the same config it always did.
38
+
39
+ Exit codes: `0` success or warnings, `1` blocking findings (a key with two different
40
+ default texts, a malformed locale file), `2` invalid configuration or usage.
41
+
42
+ A run that finds nothing to change **writes no file**: each locale file is compared
43
+ with what would be written and left alone when it matches. That makes the command safe
44
+ in a watch loop or a pre-commit hook, where rewriting identical bytes used to churn
45
+ mtimes and show work in `git status` that had not happened.
46
+
47
+ ## Programmatic use
48
+
49
+ The same functions are importable, for a repository that wants to drive extraction from
50
+ its own script:
51
+
52
+ ```ts
53
+ import { runSync, renderReport, scanSource } from '@chipmobilesdk/rn-i18n-tooling';
54
+ ```
55
+
56
+ `runSync(rootDir, config)` returns the report and whether the generated keys file
57
+ changed; `scanSource` is the scanner on its own. Both are the ones the CLI uses.
58
+
59
+ ## Repository layout
60
+
61
+ | | |
62
+ |---|---|
63
+ | `src/` | TypeScript sources; `cli.ts` is the command |
64
+ | `lib/` | build output (`npm run build`), git-ignored, published |
65
+ | `bin/chip-i18n.mjs` | the published binary, a wrapper over `lib/cli.js` |
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ // The published entry point for `npx chip-i18n`. It is a hand-written wrapper
3
+ // rather than the built `lib/cli.js` directly: a shebang in a TypeScript source
4
+ // file is at the mercy of the emitter, and this file is also where the error for
5
+ // "installed but never built" belongs.
6
+ import { existsSync } from 'node:fs';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const cli = new URL('../lib/cli.js', import.meta.url);
10
+ if (!existsSync(fileURLToPath(cli))) {
11
+ console.error(
12
+ '[chip-i18n] This package was installed without its build output. Reinstall it from npm, or run `npm run build` in packages/rn-i18n-tooling.',
13
+ );
14
+ process.exitCode = 2;
15
+ } else {
16
+ await import(cli.href);
17
+ }
package/lib/cli.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare function parseArgs(argv: readonly string[]): {
2
+ command: string;
3
+ configPath: string;
4
+ };
5
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAuBA,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAiB1F"}
package/lib/cli.js ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The `chip-i18n` CLI: extracts translation keys from an app's source and
3
+ * synchronises its locale files.
4
+ *
5
+ * npx chip-i18n sync # i18n-sync.config.json
6
+ * npx chip-i18n sync --config other.config.json
7
+ *
8
+ * Exit codes: 0 success or warnings, 1 blocking findings (conflicts or
9
+ * malformed locale files), 2 invalid configuration, bad usage or internal error.
10
+ */
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import process from 'node:process';
14
+ import { renderReport } from "./report.js";
15
+ import { SyncConfigError, runSync } from "./runner.js";
16
+ const USAGE = `Usage: chip-i18n sync [--config <path>]
17
+
18
+ sync Scan the configured source globs, update the locale files and report.
19
+ --config defaults to i18n-sync.config.json, resolved from the current
20
+ working directory.`;
21
+ export function parseArgs(argv) {
22
+ let configPath = 'i18n-sync.config.json';
23
+ // No command at all means `sync`: that is what the one command was before this
24
+ // CLI had a name, and an app's npm script may still invoke it that way.
25
+ let command = 'sync';
26
+ for (let index = 0; index < argv.length; index += 1) {
27
+ const argument = argv[index];
28
+ if (argument === '--config' && argv[index + 1]) {
29
+ configPath = argv[index + 1];
30
+ index += 1;
31
+ }
32
+ else if (argument === '--help' || argument === '-h') {
33
+ command = 'help';
34
+ }
35
+ else if (index === 0 && !argument.startsWith('-')) {
36
+ command = argument;
37
+ }
38
+ }
39
+ return { command, configPath };
40
+ }
41
+ async function main() {
42
+ const rootDir = process.cwd();
43
+ const { command, configPath } = parseArgs(process.argv.slice(2));
44
+ if (command === 'help') {
45
+ console.log(USAGE);
46
+ return 0;
47
+ }
48
+ if (command !== 'sync') {
49
+ console.error(`[chip-i18n] Unknown command: ${command}\n\n${USAGE}`);
50
+ return 2;
51
+ }
52
+ const absoluteConfigPath = path.resolve(rootDir, configPath);
53
+ if (!fs.existsSync(absoluteConfigPath)) {
54
+ console.error(`[chip-i18n] Sync configuration not found: ${configPath}`);
55
+ return 2;
56
+ }
57
+ let config;
58
+ try {
59
+ config = JSON.parse(fs.readFileSync(absoluteConfigPath, 'utf8'));
60
+ }
61
+ catch (error) {
62
+ console.error(`[chip-i18n] Sync configuration is not valid JSON: ${String(error)}`);
63
+ return 2;
64
+ }
65
+ try {
66
+ const { report, generatedKeysChanged } = await runSync(rootDir, config);
67
+ console.log(renderReport(report));
68
+ if (config.generateKeysFile) {
69
+ console.log(generatedKeysChanged
70
+ ? `Typed keys regenerated: ${config.generateKeysFile}`
71
+ : `Typed keys unchanged: ${config.generateKeysFile}`);
72
+ }
73
+ return report.outcome === 'failed' ? 1 : 0;
74
+ }
75
+ catch (error) {
76
+ if (error instanceof SyncConfigError) {
77
+ console.error(`[chip-i18n] ${error.message}`);
78
+ }
79
+ else {
80
+ console.error(`[chip-i18n] Sync failed: ${String(error)}`);
81
+ }
82
+ return 2;
83
+ }
84
+ }
85
+ main().then(code => {
86
+ process.exitCode = code;
87
+ }, error => {
88
+ console.error(`[chip-i18n] Unexpected failure: ${String(error)}`);
89
+ process.exitCode = 2;
90
+ });
91
+ //# sourceMappingURL=cli.js.map
package/lib/cli.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { RuntimeEntryConfig, TranslationTemplate } from './types.ts';
2
+ /**
3
+ * Renders the typed key contract: the union of scanned template keys and
4
+ * declared runtime-only keys (FR-041). Apps bind lookups to the generated
5
+ * type so `tsc --noEmit` reports invalid or stale key usage before release.
6
+ */
7
+ export declare function renderKeysModule(template: TranslationTemplate, runtimeEntries: readonly RuntimeEntryConfig[], typeName?: string): string;
8
+ export interface GenerateKeysResult {
9
+ filePath: string;
10
+ changed: boolean;
11
+ }
12
+ /** Writes the typed key artifact and reports whether it changed (staleness). */
13
+ export declare function generateKeysFile(rootDir: string, relativePath: string, template: TranslationTemplate, runtimeEntries: readonly RuntimeEntryConfig[], typeName?: string): GenerateKeysResult;
14
+ //# sourceMappingURL=generateKeys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generateKeys.d.ts","sourceRoot":"","sources":["../src/generateKeys.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE1E;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,mBAAmB,EAC7B,cAAc,EAAE,SAAS,kBAAkB,EAAE,EAC7C,QAAQ,SAAe,GACrB,MAAM,CA4BR;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,mBAAmB,EAC7B,cAAc,EAAE,SAAS,kBAAkB,EAAE,EAC7C,QAAQ,CAAC,EAAE,MAAM,GACf,kBAAkB,CAepB"}
@@ -0,0 +1,51 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ /**
4
+ * Renders the typed key contract: the union of scanned template keys and
5
+ * declared runtime-only keys (FR-041). Apps bind lookups to the generated
6
+ * type so `tsc --noEmit` reports invalid or stale key usage before release.
7
+ */
8
+ export function renderKeysModule(template, runtimeEntries, typeName = 'AppI18nKey') {
9
+ const templateKeys = template.keys.map(key => key.key).sort((a, b) => a.localeCompare(b, 'en'));
10
+ const runtimeKeys = runtimeEntries
11
+ .map(entry => entry.key)
12
+ .sort((a, b) => a.localeCompare(b, 'en'));
13
+ // Escaped, not interpolated: a key holding an apostrophe or a backslash wrote
14
+ // a file that did not parse, and `tsc` then failed for the whole app (I18N-20).
15
+ const literal = (key) => `'${key.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
16
+ const renderList = (keys) => keys.length === 0 ? '[]' : `[\n${keys.map(key => `\t${literal(key)},`).join('\n')}\n]`;
17
+ return [
18
+ '// AUTO-GENERATED by @chipmobilesdk/rn-i18n tooling. Do not edit by hand.',
19
+ '// Regenerate with: npm run i18n:sync',
20
+ '',
21
+ `export const I18N_TEMPLATE_KEYS = ${renderList(templateKeys)} as const;`,
22
+ '',
23
+ `export const I18N_RUNTIME_ONLY_KEYS = ${renderList(runtimeKeys)} as const;`,
24
+ '',
25
+ 'export const I18N_KEYS = [',
26
+ '\t...I18N_TEMPLATE_KEYS,',
27
+ '\t...I18N_RUNTIME_ONLY_KEYS,',
28
+ '] as const;',
29
+ '',
30
+ `export type ${typeName} = (typeof I18N_KEYS)[number];`,
31
+ '',
32
+ ].join('\n');
33
+ }
34
+ /** Writes the typed key artifact and reports whether it changed (staleness). */
35
+ export function generateKeysFile(rootDir, relativePath, template, runtimeEntries, typeName) {
36
+ const filePath = path.join(rootDir, relativePath);
37
+ const content = renderKeysModule(template, runtimeEntries, typeName);
38
+ // Line endings normalised before comparing, which is how git sees the file: a
39
+ // Windows checkout materialises CRLF, and rewriting it in LF to "fix" that is
40
+ // churn, not work. The file keeps its endings while the content matches.
41
+ const previous = fs.existsSync(filePath)
42
+ ? fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n')
43
+ : undefined;
44
+ const changed = previous !== content;
45
+ if (changed) {
46
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
47
+ fs.writeFileSync(filePath, content, 'utf8');
48
+ }
49
+ return { filePath, changed };
50
+ }
51
+ //# sourceMappingURL=generateKeys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generateKeys.js","sourceRoot":"","sources":["../src/generateKeys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC/B,QAA6B,EAC7B,cAA6C,EAC7C,QAAQ,GAAG,YAAY;IAEvB,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAChG,MAAM,WAAW,GAAG,cAAc;SAChC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC;SACvB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAE3C,8EAA8E;IAC9E,gFAAgF;IAChF,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;IACxF,MAAM,UAAU,GAAG,CAAC,IAAuB,EAAE,EAAE,CAC9C,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;IAExF,OAAO;QACN,2EAA2E;QAC3E,uCAAuC;QACvC,EAAE;QACF,qCAAqC,UAAU,CAAC,YAAY,CAAC,YAAY;QACzE,EAAE;QACF,yCAAyC,UAAU,CAAC,WAAW,CAAC,YAAY;QAC5E,EAAE;QACF,4BAA4B;QAC5B,0BAA0B;QAC1B,8BAA8B;QAC9B,aAAa;QACb,EAAE;QACF,eAAe,QAAQ,gCAAgC;QACvD,EAAE;KACF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC;AAOD,gFAAgF;AAChF,MAAM,UAAU,gBAAgB,CAC/B,OAAe,EACf,YAAoB,EACpB,QAA6B,EAC7B,cAA6C,EAC7C,QAAiB;IAEjB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,gBAAgB,CAAC,QAAQ,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC;IACrE,8EAA8E;IAC9E,8EAA8E;IAC9E,yEAAyE;IACzE,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QACvC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC;QAC1D,CAAC,CAAC,SAAS,CAAC;IACb,MAAM,OAAO,GAAG,QAAQ,KAAK,OAAO,CAAC;IACrC,IAAI,OAAO,EAAE,CAAC;QACb,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Programmatic surface of `@chipmobilesdk/rn-i18n-tooling`: the extraction and
3
+ * synchronization the `chip-i18n` command performs, for a repository that drives
4
+ * it from its own script. Node only — nothing here may be imported from React
5
+ * Native code, which is why it is a package of its own rather than a subpath of
6
+ * the runtime library (LIB-003, WS4-03).
7
+ */
8
+ export { globToRegExp, listSourceFiles, scanSource } from './scan.ts';
9
+ export { flattenTranslations, nestTranslations, syncLocaleFiles } from './sync.ts';
10
+ export { buildReport, renderReport } from './report.ts';
11
+ export { generateKeysFile, renderKeysModule } from './generateKeys.ts';
12
+ export type { GenerateKeysResult } from './generateKeys.ts';
13
+ export { SyncConfigError, loadRuntimeEntries, runSync, validateSyncConfig } from './runner.ts';
14
+ export type { SyncRunResult } from './runner.ts';
15
+ export type { AmbiguousUsage, ExtractionReport, KeyConflict, LocaleSyncResult, PatternConfig, ReportOutcome, RuntimeEntryConfig, SourceLocation, SyncConfig, TemplateKey, TranslationTemplate, } from './types.ts';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACvE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC/F,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EACX,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,WAAW,EACX,mBAAmB,GACnB,MAAM,YAAY,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Programmatic surface of `@chipmobilesdk/rn-i18n-tooling`: the extraction and
3
+ * synchronization the `chip-i18n` command performs, for a repository that drives
4
+ * it from its own script. Node only — nothing here may be imported from React
5
+ * Native code, which is why it is a package of its own rather than a subpath of
6
+ * the runtime library (LIB-003, WS4-03).
7
+ */
8
+ export { globToRegExp, listSourceFiles, scanSource } from "./scan.js";
9
+ export { flattenTranslations, nestTranslations, syncLocaleFiles } from "./sync.js";
10
+ export { buildReport, renderReport } from "./report.js";
11
+ export { generateKeysFile, renderKeysModule } from "./generateKeys.js";
12
+ export { SyncConfigError, loadRuntimeEntries, runSync, validateSyncConfig } from "./runner.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ExtractionReport, LocaleSyncResult, RuntimeEntryConfig, TranslationTemplate } from './types.ts';
2
+ /**
3
+ * Assembles the extraction/synchronization report distinguishing scanned
4
+ * template keys, runtime-only keys, stale removals, ambiguous usages, and
5
+ * conflicts (FR-026, FR-036).
6
+ */
7
+ export declare function buildReport(template: TranslationTemplate, syncResults: readonly LocaleSyncResult[], runtimeEntries: readonly RuntimeEntryConfig[]): ExtractionReport;
8
+ /** Renders the report as readable text for CLI output. */
9
+ export declare function renderReport(report: ExtractionReport): string;
10
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,gBAAgB,EAChB,gBAAgB,EAEhB,kBAAkB,EAClB,mBAAmB,EACnB,MAAM,YAAY,CAAC;AAapB;;;;GAIG;AACH,wBAAgB,WAAW,CAC1B,QAAQ,EAAE,mBAAmB,EAC7B,WAAW,EAAE,SAAS,gBAAgB,EAAE,EACxC,cAAc,EAAE,SAAS,kBAAkB,EAAE,GAC3C,gBAAgB,CA2ClB;AAED,0DAA0D;AAC1D,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAwC7D"}
package/lib/report.js ADDED
@@ -0,0 +1,89 @@
1
+ function perLocale(results, select) {
2
+ const record = {};
3
+ for (const result of results) {
4
+ record[result.locale] = select(result);
5
+ }
6
+ return record;
7
+ }
8
+ /**
9
+ * Assembles the extraction/synchronization report distinguishing scanned
10
+ * template keys, runtime-only keys, stale removals, ambiguous usages, and
11
+ * conflicts (FR-026, FR-036).
12
+ */
13
+ export function buildReport(template, syncResults, runtimeEntries) {
14
+ const errors = syncResults.filter(result => result.error).map(result => result.error);
15
+ let outcome = 'success';
16
+ if (template.ambiguous.length > 0 ||
17
+ syncResults.some(result => result.addedKeys.length > 0 || result.runtimeReported.length > 0)) {
18
+ outcome = 'completedWithWarnings';
19
+ }
20
+ if (errors.length > 0 || template.conflicts.length > 0) {
21
+ outcome = 'failed';
22
+ }
23
+ return {
24
+ outcome,
25
+ discovered: template.keys.map(key => ({
26
+ key: key.key,
27
+ defaultText: key.defaultText,
28
+ locations: key.locations.map(location => `${location.file}:${location.line}`),
29
+ })),
30
+ createdLocaleFiles: syncResults
31
+ .filter(result => result.created && !result.error)
32
+ .map(result => ({ locale: result.locale, filePath: result.filePath })),
33
+ writtenLocaleFiles: syncResults.filter(result => result.written).map(result => result.locale),
34
+ addedKeys: perLocale(syncResults, result => result.addedKeys),
35
+ preservedKeys: perLocale(syncResults, result => result.preservedKeys),
36
+ removedStaleKeys: perLocale(syncResults, result => result.removedKeys),
37
+ missingPerLocale: perLocale(syncResults, result => [...result.addedKeys, ...result.runtimeAdded, ...result.runtimeReported].sort((a, b) => a.localeCompare(b, 'en'))),
38
+ runtimeOnly: {
39
+ declared: runtimeEntries.map(entry => entry.key),
40
+ preserved: perLocale(syncResults, result => result.runtimePreserved),
41
+ added: perLocale(syncResults, result => result.runtimeAdded),
42
+ reported: perLocale(syncResults, result => result.runtimeReported),
43
+ },
44
+ ambiguous: template.ambiguous,
45
+ conflicts: template.conflicts,
46
+ errors,
47
+ };
48
+ }
49
+ /** Renders the report as readable text for CLI output. */
50
+ export function renderReport(report) {
51
+ const lines = [];
52
+ lines.push(`i18n sync outcome: ${report.outcome}`);
53
+ lines.push(`Discovered keys: ${report.discovered.length}`);
54
+ lines.push(report.writtenLocaleFiles.length === 0
55
+ ? 'Locale files: unchanged, nothing written'
56
+ : `Locale files written: ${report.writtenLocaleFiles.join(', ')}`);
57
+ for (const created of report.createdLocaleFiles) {
58
+ lines.push(`Created locale file: ${created.locale} -> ${created.filePath}`);
59
+ }
60
+ for (const [locale, keys] of Object.entries(report.addedKeys)) {
61
+ if (keys.length > 0) {
62
+ lines.push(`Added [${locale}]: ${keys.join(', ')}`);
63
+ }
64
+ }
65
+ for (const [locale, keys] of Object.entries(report.removedStaleKeys)) {
66
+ if (keys.length > 0) {
67
+ lines.push(`Removed stale [${locale}]: ${keys.join(', ')}`);
68
+ }
69
+ }
70
+ for (const [locale, keys] of Object.entries(report.runtimeOnly.reported)) {
71
+ if (keys.length > 0) {
72
+ lines.push(`Runtime-only missing [${locale}]: ${keys.join(', ')}`);
73
+ }
74
+ }
75
+ for (const usage of report.ambiguous) {
76
+ lines.push(`Ambiguous usage at ${usage.file}:${usage.line}: ${usage.snippet}`);
77
+ }
78
+ for (const conflict of report.conflicts) {
79
+ const sources = conflict.values
80
+ .map(value => `"${value.text}" (${value.location.file}:${value.location.line})`)
81
+ .join(' vs ');
82
+ lines.push(`Conflict for key "${conflict.key}": ${sources}`);
83
+ }
84
+ for (const error of report.errors) {
85
+ lines.push(`Error: ${error}`);
86
+ }
87
+ return lines.join('\n');
88
+ }
89
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { ExtractionReport, RuntimeEntryConfig, SyncConfig } from './types.ts';
2
+ export declare class SyncConfigError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare function validateSyncConfig(config: SyncConfig): void;
6
+ /**
7
+ * Loads declared runtime-only entries from the configured module. Accepts a
8
+ * default export or a named `runtimeEntries` export. The module must use
9
+ * type-only imports so it can run under Node's TypeScript type stripping.
10
+ */
11
+ export declare function loadRuntimeEntries(rootDir: string, runtimeEntriesFile: string | undefined): Promise<RuntimeEntryConfig[]>;
12
+ export interface SyncRunResult {
13
+ report: ExtractionReport;
14
+ generatedKeysChanged: boolean;
15
+ }
16
+ /**
17
+ * Full pipeline: scan the source tree, synchronize every locale JSON file,
18
+ * regenerate the typed key artifact, and assemble the report.
19
+ */
20
+ export declare function runSync(rootDir: string, config: SyncConfig, runtimeEntriesOverride?: readonly RuntimeEntryConfig[]): Promise<SyncRunResult>;
21
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEnF,qBAAa,eAAgB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI3B;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAuB3D;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,MAAM,EACf,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACpC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAmB/B;AAED,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,gBAAgB,CAAC;IACzB,oBAAoB,EAAE,OAAO,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,UAAU,EAClB,sBAAsB,CAAC,EAAE,SAAS,kBAAkB,EAAE,GACpD,OAAO,CAAC,aAAa,CAAC,CA2BxB"}
package/lib/runner.js ADDED
@@ -0,0 +1,88 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { pathToFileURL } from 'node:url';
12
+ import { scanSource } from "./scan.js";
13
+ import { syncLocaleFiles } from "./sync.js";
14
+ import { buildReport } from "./report.js";
15
+ import { generateKeysFile } from "./generateKeys.js";
16
+ export class SyncConfigError extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = 'SyncConfigError';
20
+ }
21
+ }
22
+ export function validateSyncConfig(config) {
23
+ const issues = [];
24
+ if (!Array.isArray(config.sourceGlobs) || config.sourceGlobs.length === 0) {
25
+ issues.push('`sourceGlobs` must list at least one glob.');
26
+ }
27
+ if (!Array.isArray(config.patterns) || config.patterns.length === 0) {
28
+ issues.push('`patterns` must list at least one regex pattern.');
29
+ }
30
+ else {
31
+ for (const pattern of config.patterns) {
32
+ if (!pattern.regex?.includes('(?<key>')) {
33
+ issues.push(`Pattern "${pattern.regex}" must contain a named (?<key>...) capture group.`);
34
+ }
35
+ }
36
+ }
37
+ if (!config.localesDir) {
38
+ issues.push('`localesDir` is required.');
39
+ }
40
+ if (!Array.isArray(config.locales) || config.locales.length === 0) {
41
+ issues.push('`locales` must list every supported locale.');
42
+ }
43
+ if (issues.length > 0) {
44
+ throw new SyncConfigError(`Invalid i18n sync configuration:\n- ${issues.join('\n- ')}`);
45
+ }
46
+ }
47
+ /**
48
+ * Loads declared runtime-only entries from the configured module. Accepts a
49
+ * default export or a named `runtimeEntries` export. The module must use
50
+ * type-only imports so it can run under Node's TypeScript type stripping.
51
+ */
52
+ export async function loadRuntimeEntries(rootDir, runtimeEntriesFile) {
53
+ if (!runtimeEntriesFile) {
54
+ return [];
55
+ }
56
+ const absolutePath = path.resolve(rootDir, runtimeEntriesFile);
57
+ if (!fs.existsSync(absolutePath)) {
58
+ throw new SyncConfigError(`Runtime entries file not found: ${runtimeEntriesFile}`);
59
+ }
60
+ const module = (await import(__rewriteRelativeImportExtension(pathToFileURL(absolutePath).href)));
61
+ const entries = module.default ?? module.runtimeEntries;
62
+ if (!Array.isArray(entries)) {
63
+ throw new SyncConfigError(`Runtime entries file must export an entry array (default or \`runtimeEntries\`): ${runtimeEntriesFile}`);
64
+ }
65
+ return entries;
66
+ }
67
+ /**
68
+ * Full pipeline: scan the source tree, synchronize every locale JSON file,
69
+ * regenerate the typed key artifact, and assemble the report.
70
+ */
71
+ export async function runSync(rootDir, config, runtimeEntriesOverride) {
72
+ validateSyncConfig(config);
73
+ const runtimeEntries = runtimeEntriesOverride ?? (await loadRuntimeEntries(rootDir, config.runtimeEntriesFile));
74
+ const template = scanSource(rootDir, config.sourceGlobs, config.patterns);
75
+ const syncResults = syncLocaleFiles(rootDir, config, template, runtimeEntries);
76
+ const report = buildReport(template, syncResults, runtimeEntries);
77
+ let generatedKeysChanged = false;
78
+ if (config.generateKeysFile) {
79
+ generatedKeysChanged = generateKeysFile(rootDir, config.generateKeysFile, template, runtimeEntries, config.keysTypeName).changed;
80
+ }
81
+ if (config.reportPath) {
82
+ const reportPath = path.resolve(rootDir, config.reportPath);
83
+ fs.mkdirSync(path.dirname(reportPath), { recursive: true });
84
+ fs.writeFileSync(reportPath, `${JSON.stringify(report, null, '\t')}\n`, 'utf8');
85
+ }
86
+ return { report, generatedKeysChanged };
87
+ }
88
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}