i18nsmith 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 (213) hide show
  1. package/dist/commands/audit.d.ts +3 -0
  2. package/dist/commands/audit.d.ts.map +1 -0
  3. package/dist/commands/audit.js +180 -0
  4. package/dist/commands/audit.js.map +1 -0
  5. package/dist/commands/backup.d.ts +6 -0
  6. package/dist/commands/backup.d.ts.map +1 -0
  7. package/dist/commands/backup.js +85 -0
  8. package/dist/commands/backup.js.map +1 -0
  9. package/dist/commands/check.d.ts +3 -0
  10. package/dist/commands/check.d.ts.map +1 -0
  11. package/dist/commands/check.js +151 -0
  12. package/dist/commands/check.js.map +1 -0
  13. package/dist/commands/config.d.ts +3 -0
  14. package/dist/commands/config.d.ts.map +1 -0
  15. package/dist/commands/config.js +235 -0
  16. package/dist/commands/config.js.map +1 -0
  17. package/dist/commands/debug-patterns.d.ts +3 -0
  18. package/dist/commands/debug-patterns.d.ts.map +1 -0
  19. package/dist/commands/debug-patterns.js +192 -0
  20. package/dist/commands/debug-patterns.js.map +1 -0
  21. package/dist/commands/debug-patterns.test.d.ts +2 -0
  22. package/dist/commands/debug-patterns.test.d.ts.map +1 -0
  23. package/dist/commands/debug-patterns.test.js +109 -0
  24. package/dist/commands/debug-patterns.test.js.map +1 -0
  25. package/dist/commands/diagnose.d.ts +3 -0
  26. package/dist/commands/diagnose.d.ts.map +1 -0
  27. package/dist/commands/diagnose.js +117 -0
  28. package/dist/commands/diagnose.js.map +1 -0
  29. package/dist/commands/init.d.ts +8 -0
  30. package/dist/commands/init.d.ts.map +1 -0
  31. package/dist/commands/init.js +450 -0
  32. package/dist/commands/init.js.map +1 -0
  33. package/dist/commands/init.test.d.ts +2 -0
  34. package/dist/commands/init.test.d.ts.map +1 -0
  35. package/dist/commands/init.test.js +74 -0
  36. package/dist/commands/init.test.js.map +1 -0
  37. package/dist/commands/install-hooks.d.ts +3 -0
  38. package/dist/commands/install-hooks.d.ts.map +1 -0
  39. package/dist/commands/install-hooks.js +52 -0
  40. package/dist/commands/install-hooks.js.map +1 -0
  41. package/dist/commands/preflight.d.ts +7 -0
  42. package/dist/commands/preflight.d.ts.map +1 -0
  43. package/dist/commands/preflight.js +417 -0
  44. package/dist/commands/preflight.js.map +1 -0
  45. package/dist/commands/preflight.test.d.ts +5 -0
  46. package/dist/commands/preflight.test.d.ts.map +1 -0
  47. package/dist/commands/preflight.test.js +108 -0
  48. package/dist/commands/preflight.test.js.map +1 -0
  49. package/dist/commands/rename.d.ts +6 -0
  50. package/dist/commands/rename.d.ts.map +1 -0
  51. package/dist/commands/rename.js +204 -0
  52. package/dist/commands/rename.js.map +1 -0
  53. package/dist/commands/scaffold-adapter.d.ts +3 -0
  54. package/dist/commands/scaffold-adapter.d.ts.map +1 -0
  55. package/dist/commands/scaffold-adapter.js +204 -0
  56. package/dist/commands/scaffold-adapter.js.map +1 -0
  57. package/dist/commands/scaffold-adapter.test.d.ts +2 -0
  58. package/dist/commands/scaffold-adapter.test.d.ts.map +1 -0
  59. package/dist/commands/scaffold-adapter.test.js +102 -0
  60. package/dist/commands/scaffold-adapter.test.js.map +1 -0
  61. package/dist/commands/scan.d.ts +3 -0
  62. package/dist/commands/scan.d.ts.map +1 -0
  63. package/dist/commands/scan.js +93 -0
  64. package/dist/commands/scan.js.map +1 -0
  65. package/dist/commands/sync-seed.test.d.ts +2 -0
  66. package/dist/commands/sync-seed.test.d.ts.map +1 -0
  67. package/dist/commands/sync-seed.test.js +86 -0
  68. package/dist/commands/sync-seed.test.js.map +1 -0
  69. package/dist/commands/sync.d.ts +3 -0
  70. package/dist/commands/sync.d.ts.map +1 -0
  71. package/dist/commands/sync.js +590 -0
  72. package/dist/commands/sync.js.map +1 -0
  73. package/dist/commands/transform.d.ts +3 -0
  74. package/dist/commands/transform.d.ts.map +1 -0
  75. package/dist/commands/transform.js +114 -0
  76. package/dist/commands/transform.js.map +1 -0
  77. package/dist/commands/translate/csv-handler.d.ts +21 -0
  78. package/dist/commands/translate/csv-handler.d.ts.map +1 -0
  79. package/dist/commands/translate/csv-handler.js +270 -0
  80. package/dist/commands/translate/csv-handler.js.map +1 -0
  81. package/dist/commands/translate/executor.d.ts +31 -0
  82. package/dist/commands/translate/executor.d.ts.map +1 -0
  83. package/dist/commands/translate/executor.js +117 -0
  84. package/dist/commands/translate/executor.js.map +1 -0
  85. package/dist/commands/translate/index.d.ts +10 -0
  86. package/dist/commands/translate/index.d.ts.map +1 -0
  87. package/dist/commands/translate/index.js +170 -0
  88. package/dist/commands/translate/index.js.map +1 -0
  89. package/dist/commands/translate/reporter.d.ts +29 -0
  90. package/dist/commands/translate/reporter.d.ts.map +1 -0
  91. package/dist/commands/translate/reporter.js +103 -0
  92. package/dist/commands/translate/reporter.js.map +1 -0
  93. package/dist/commands/translate/types.d.ts +50 -0
  94. package/dist/commands/translate/types.d.ts.map +1 -0
  95. package/dist/commands/translate/types.js +5 -0
  96. package/dist/commands/translate/types.js.map +1 -0
  97. package/dist/commands/translate.d.ts +7 -0
  98. package/dist/commands/translate.d.ts.map +1 -0
  99. package/dist/commands/translate.js +7 -0
  100. package/dist/commands/translate.js.map +1 -0
  101. package/dist/commands/translate.test.d.ts +2 -0
  102. package/dist/commands/translate.test.d.ts.map +1 -0
  103. package/dist/commands/translate.test.js +118 -0
  104. package/dist/commands/translate.test.js.map +1 -0
  105. package/dist/e2e.test.d.ts +6 -0
  106. package/dist/e2e.test.d.ts.map +1 -0
  107. package/dist/e2e.test.js +376 -0
  108. package/dist/e2e.test.js.map +1 -0
  109. package/dist/index.d.ts +4 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +39 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/integration.test.d.ts +6 -0
  114. package/dist/integration.test.d.ts.map +1 -0
  115. package/dist/integration.test.js +320 -0
  116. package/dist/integration.test.js.map +1 -0
  117. package/dist/utils/diagnostics-exit.d.ts +12 -0
  118. package/dist/utils/diagnostics-exit.d.ts.map +1 -0
  119. package/dist/utils/diagnostics-exit.js +49 -0
  120. package/dist/utils/diagnostics-exit.js.map +1 -0
  121. package/dist/utils/diagnostics-exit.test.d.ts +2 -0
  122. package/dist/utils/diagnostics-exit.test.d.ts.map +1 -0
  123. package/dist/utils/diagnostics-exit.test.js +40 -0
  124. package/dist/utils/diagnostics-exit.test.js.map +1 -0
  125. package/dist/utils/diff-utils.d.ts +4 -0
  126. package/dist/utils/diff-utils.d.ts.map +1 -0
  127. package/dist/utils/diff-utils.js +30 -0
  128. package/dist/utils/diff-utils.js.map +1 -0
  129. package/dist/utils/diff-utils.test.d.ts +2 -0
  130. package/dist/utils/diff-utils.test.d.ts.map +1 -0
  131. package/dist/utils/diff-utils.test.js +30 -0
  132. package/dist/utils/diff-utils.test.js.map +1 -0
  133. package/dist/utils/exit-codes.d.ts +142 -0
  134. package/dist/utils/exit-codes.d.ts.map +1 -0
  135. package/dist/utils/exit-codes.js +168 -0
  136. package/dist/utils/exit-codes.js.map +1 -0
  137. package/dist/utils/package-manager.d.ts +4 -0
  138. package/dist/utils/package-manager.d.ts.map +1 -0
  139. package/dist/utils/package-manager.js +40 -0
  140. package/dist/utils/package-manager.js.map +1 -0
  141. package/dist/utils/pkg.d.ts +3 -0
  142. package/dist/utils/pkg.d.ts.map +1 -0
  143. package/dist/utils/pkg.js +24 -0
  144. package/dist/utils/pkg.js.map +1 -0
  145. package/dist/utils/provider-injector.d.ts +36 -0
  146. package/dist/utils/provider-injector.d.ts.map +1 -0
  147. package/dist/utils/provider-injector.js +223 -0
  148. package/dist/utils/provider-injector.js.map +1 -0
  149. package/dist/utils/provider-injector.test.d.ts +2 -0
  150. package/dist/utils/provider-injector.test.d.ts.map +1 -0
  151. package/dist/utils/provider-injector.test.js +67 -0
  152. package/dist/utils/provider-injector.test.js.map +1 -0
  153. package/dist/utils/scaffold.d.ts +20 -0
  154. package/dist/utils/scaffold.d.ts.map +1 -0
  155. package/dist/utils/scaffold.js +197 -0
  156. package/dist/utils/scaffold.js.map +1 -0
  157. package/package.json +35 -0
  158. package/src/commands/audit.ts +234 -0
  159. package/src/commands/backup.ts +96 -0
  160. package/src/commands/check.ts +191 -0
  161. package/src/commands/config.ts +263 -0
  162. package/src/commands/debug-patterns.test.ts +134 -0
  163. package/src/commands/debug-patterns.ts +257 -0
  164. package/src/commands/diagnose.ts +136 -0
  165. package/src/commands/init.test.ts +82 -0
  166. package/src/commands/init.ts +536 -0
  167. package/src/commands/install-hooks.ts +66 -0
  168. package/src/commands/preflight.test.ts +139 -0
  169. package/src/commands/preflight.ts +488 -0
  170. package/src/commands/rename.ts +264 -0
  171. package/src/commands/scaffold-adapter.test.ts +110 -0
  172. package/src/commands/scaffold-adapter.ts +250 -0
  173. package/src/commands/scan.ts +125 -0
  174. package/src/commands/sync-seed.test.ts +116 -0
  175. package/src/commands/sync.ts +736 -0
  176. package/src/commands/transform.ts +151 -0
  177. package/src/commands/translate/README.md +75 -0
  178. package/src/commands/translate/csv-handler.ts +301 -0
  179. package/src/commands/translate/executor.ts +188 -0
  180. package/src/commands/translate/index.ts +220 -0
  181. package/src/commands/translate/reporter.ts +138 -0
  182. package/src/commands/translate/types.ts +56 -0
  183. package/src/commands/translate.test.ts +173 -0
  184. package/src/commands/translate.ts +6 -0
  185. package/src/e2e.test.ts +479 -0
  186. package/src/fixtures/README.md +61 -0
  187. package/src/fixtures/basic-react/i18n.config.json +15 -0
  188. package/src/fixtures/basic-react/locales/de.json +8 -0
  189. package/src/fixtures/basic-react/locales/en.json +8 -0
  190. package/src/fixtures/basic-react/locales/fr.json +8 -0
  191. package/src/fixtures/basic-react/src/App.tsx +15 -0
  192. package/src/fixtures/basic-react/src/Messages.tsx +12 -0
  193. package/src/fixtures/nested-locales/i18n.config.json +9 -0
  194. package/src/fixtures/nested-locales/locales/en.json +23 -0
  195. package/src/fixtures/nested-locales/locales/fr.json +23 -0
  196. package/src/fixtures/nested-locales/src/HomePage.tsx +13 -0
  197. package/src/fixtures/suspicious-keys/i18n.config.json +9 -0
  198. package/src/fixtures/suspicious-keys/locales/en.json +11 -0
  199. package/src/fixtures/suspicious-keys/locales/fr.json +11 -0
  200. package/src/fixtures/suspicious-keys/src/BadKeys.tsx +19 -0
  201. package/src/index.ts +43 -0
  202. package/src/integration.test.ts +438 -0
  203. package/src/utils/diagnostics-exit.test.ts +47 -0
  204. package/src/utils/diagnostics-exit.ts +63 -0
  205. package/src/utils/diff-utils.test.ts +36 -0
  206. package/src/utils/diff-utils.ts +42 -0
  207. package/src/utils/exit-codes.ts +201 -0
  208. package/src/utils/package-manager.ts +44 -0
  209. package/src/utils/pkg.ts +23 -0
  210. package/src/utils/provider-injector.test.ts +79 -0
  211. package/src/utils/provider-injector.ts +315 -0
  212. package/src/utils/scaffold.ts +240 -0
  213. package/tsconfig.json +17 -0
@@ -0,0 +1,125 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import type { Command } from 'commander';
5
+ import { loadConfigWithMeta, Scanner } from '@i18nsmith/core';
6
+ import type { ScanCandidate } from '@i18nsmith/core';
7
+
8
+ interface ScanOptions {
9
+ config?: string;
10
+ json?: boolean;
11
+ target?: string[];
12
+ report?: string;
13
+ listFiles?: boolean;
14
+ include?: string[];
15
+ exclude?: string[];
16
+ }
17
+
18
+ const collectTargetPatterns = (value: string | string[], previous: string[]) => {
19
+ const list = Array.isArray(value) ? value : [value];
20
+ const tokens = list
21
+ .flatMap((entry) => entry.split(','))
22
+ .map((token) => token.trim())
23
+ .filter(Boolean);
24
+ return [...previous, ...tokens];
25
+ };
26
+
27
+ function printCandidateTable(candidates: ScanCandidate[]) {
28
+ const preview = candidates.slice(0, 50).map((candidate) => ({
29
+ File: candidate.filePath,
30
+ Line: candidate.position.line,
31
+ Column: candidate.position.column,
32
+ Kind: candidate.kind,
33
+ Context: candidate.context ?? '',
34
+ Text:
35
+ candidate.text.length > 60
36
+ ? `${candidate.text.slice(0, 57)}...`
37
+ : candidate.text,
38
+ }));
39
+
40
+ console.table(preview);
41
+
42
+ if (candidates.length > 50) {
43
+ console.log(chalk.gray(`Showing first 50 of ${candidates.length} candidates.`));
44
+ }
45
+ }
46
+
47
+ export function registerScan(program: Command) {
48
+ program
49
+ .command('scan')
50
+ .description('Scan project for strings to translate')
51
+ .option('-c, --config <path>', 'Path to i18nsmith config file', 'i18n.config.json')
52
+ .option('--json', 'Print raw JSON results', false)
53
+ .option('--report <path>', 'Write JSON summary to a file (for CI or editors)')
54
+ .option('--list-files', 'List the files that were scanned', false)
55
+ .option('--include <patterns...>', 'Override include globs from config (comma or space separated)', collectTargetPatterns, [])
56
+ .option('--exclude <patterns...>', 'Override exclude globs from config (comma or space separated)', collectTargetPatterns, [])
57
+ .action(async (options: ScanOptions) => {
58
+ console.log(chalk.blue('Starting scan...'));
59
+
60
+ try {
61
+ const { config, projectRoot, configPath } = await loadConfigWithMeta(options.config);
62
+
63
+ // Inform user if config was found in a parent directory
64
+ const cwd = process.cwd();
65
+ if (projectRoot !== cwd) {
66
+ console.log(chalk.gray(`Config found at ${path.relative(cwd, configPath)}`));
67
+ console.log(chalk.gray(`Using project root: ${projectRoot}\n`));
68
+ }
69
+
70
+ if (options.include?.length) {
71
+ config.include = options.include;
72
+ }
73
+ if (options.exclude?.length) {
74
+ config.exclude = options.exclude;
75
+ }
76
+ const scanner = new Scanner(config, { workspaceRoot: projectRoot });
77
+ const summary = scanner.scan();
78
+
79
+ if (options.report) {
80
+ const outputPath = path.resolve(process.cwd(), options.report);
81
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
82
+ await fs.writeFile(outputPath, JSON.stringify(summary, null, 2));
83
+ console.log(chalk.green(`Scan report written to ${outputPath}`));
84
+ }
85
+
86
+ if (options.json) {
87
+ console.log(JSON.stringify(summary, null, 2));
88
+ return;
89
+ }
90
+
91
+ console.log(
92
+ chalk.green(
93
+ `Scanned ${summary.filesScanned} file${summary.filesScanned === 1 ? '' : 's'} and found ${summary.candidates.length} candidate${summary.candidates.length === 1 ? '' : 's'}.`
94
+ )
95
+ );
96
+
97
+ if (summary.candidates.length === 0) {
98
+ console.log(chalk.yellow('No translatable strings found.'));
99
+ return;
100
+ }
101
+
102
+ printCandidateTable(summary.candidates);
103
+
104
+ if (options.listFiles) {
105
+ if (summary.filesExamined.length === 0) {
106
+ console.log(chalk.yellow('No files matched the configured include/exclude patterns.'));
107
+ } else {
108
+ console.log(chalk.blue(`Files scanned (${summary.filesExamined.length}):`));
109
+ const preview = summary.filesExamined.slice(0, 200);
110
+ preview.forEach((file) => console.log(` • ${file}`));
111
+ if (summary.filesExamined.length > preview.length) {
112
+ console.log(
113
+ chalk.gray(
114
+ ` ...and ${summary.filesExamined.length - preview.length} more. Use --target to narrow the list.`
115
+ )
116
+ );
117
+ }
118
+ }
119
+ }
120
+ } catch (error) {
121
+ console.error(chalk.red('Scan failed:'), (error as Error).message);
122
+ process.exitCode = 1;
123
+ }
124
+ });
125
+ }
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { execSync } from 'child_process';
6
+
7
+ describe('sync --seed-target-locales', () => {
8
+ let tempDir: string;
9
+
10
+ beforeEach(async () => {
11
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'i18nsmith-seed-test-'));
12
+
13
+ // Create basic project structure
14
+ await fs.mkdir(path.join(tempDir, 'src'), { recursive: true });
15
+ await fs.mkdir(path.join(tempDir, 'locales'), { recursive: true });
16
+
17
+ // Create source file with translation calls
18
+ await fs.writeFile(
19
+ path.join(tempDir, 'src', 'App.tsx'),
20
+ `import { useTranslation } from 'react-i18next';
21
+ export function App() {
22
+ const { t } = useTranslation();
23
+ return <div>{t('greeting')}{t('farewell')}</div>;
24
+ }
25
+ `
26
+ );
27
+
28
+ // Create source locale with both keys
29
+ await fs.writeFile(
30
+ path.join(tempDir, 'locales', 'en.json'),
31
+ JSON.stringify({ greeting: 'Hello', farewell: 'Goodbye' }, null, 2)
32
+ );
33
+
34
+ // Create empty target locale
35
+ await fs.writeFile(
36
+ path.join(tempDir, 'locales', 'fr.json'),
37
+ JSON.stringify({}, null, 2)
38
+ );
39
+
40
+ // Create config
41
+ await fs.writeFile(
42
+ path.join(tempDir, 'i18n.config.json'),
43
+ JSON.stringify({
44
+ version: 1,
45
+ sourceLanguage: 'en',
46
+ targetLanguages: ['fr'],
47
+ localesDir: 'locales',
48
+ include: ['src/**/*.{ts,tsx}'],
49
+ }, null, 2)
50
+ );
51
+ });
52
+
53
+ afterEach(async () => {
54
+ await fs.rm(tempDir, { recursive: true, force: true });
55
+ });
56
+
57
+ const runCli = (args: string): string => {
58
+ const cliPath = path.resolve(__dirname, '..', '..', 'dist', 'index.js');
59
+ try {
60
+ return execSync(`node ${cliPath} ${args}`, {
61
+ cwd: tempDir,
62
+ encoding: 'utf8',
63
+ stdio: ['pipe', 'pipe', 'pipe'],
64
+ });
65
+ } catch (error) {
66
+ const err = error as { stdout?: string; stderr?: string };
67
+ return (err.stdout ?? '') + (err.stderr ?? '');
68
+ }
69
+ };
70
+
71
+ it('seeds target locales with empty string by default', async () => {
72
+ const output = runCli('sync --write --seed-target-locales');
73
+ expect(output).toContain('Syncing');
74
+
75
+ const frContent = await fs.readFile(path.join(tempDir, 'locales', 'fr.json'), 'utf8');
76
+ const frData = JSON.parse(frContent);
77
+
78
+ // Should have both keys seeded in target locale
79
+ expect(frData.greeting).toBe('');
80
+ expect(frData.farewell).toBe('');
81
+ });
82
+
83
+ it('seeds target locales with custom seed value', async () => {
84
+ const output = runCli('sync --write --seed-target-locales --seed-value "[TODO]"');
85
+ expect(output).toContain('Syncing');
86
+
87
+ const frContent = await fs.readFile(path.join(tempDir, 'locales', 'fr.json'), 'utf8');
88
+ const frData = JSON.parse(frContent);
89
+
90
+ // Should have both keys seeded with [TODO]
91
+ expect(frData.greeting).toBe('[TODO]');
92
+ expect(frData.farewell).toBe('[TODO]');
93
+ });
94
+
95
+ it('does not seed when flag is not provided', async () => {
96
+ const output = runCli('sync --write');
97
+ expect(output).toContain('Syncing');
98
+
99
+ const frContent = await fs.readFile(path.join(tempDir, 'locales', 'fr.json'), 'utf8');
100
+ const frData = JSON.parse(frContent);
101
+
102
+ // Target locale should remain empty (no seeding)
103
+ expect(Object.keys(frData).length).toBe(0);
104
+ });
105
+
106
+ it('dry-run shows what would be seeded', async () => {
107
+ const output = runCli('sync --seed-target-locales');
108
+ expect(output).toContain('DRY RUN');
109
+ expect(output).toContain('missing');
110
+
111
+ // File should not be modified
112
+ const frContent = await fs.readFile(path.join(tempDir, 'locales', 'fr.json'), 'utf8');
113
+ const frData = JSON.parse(frContent);
114
+ expect(Object.keys(frData).length).toBe(0);
115
+ });
116
+ });