@jsarc/initiator 0.0.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/intl.ts ADDED
@@ -0,0 +1,353 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import * as ts from 'typescript';
5
+ import {
6
+ Pajo,
7
+ } from '@jsarc/pajo';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ export interface TranslationKey {
13
+ key: string;
14
+ module?: string;
15
+ defaultValue?: string;
16
+ context?: string;
17
+ count?: boolean;
18
+ }
19
+
20
+ export interface TranslationConfig {
21
+ srcDir: string;
22
+ supportedLocales: string[];
23
+ outputFile: string;
24
+ modulesDir: string;
25
+ localesDir: string;
26
+ }
27
+
28
+ export class TranslationGenerator {
29
+ private config: TranslationConfig;
30
+ private keys: TranslationKey[] = [];
31
+ private modules: Set<string> = new Set();
32
+ private importedModules: Set<string> = new Set();
33
+
34
+ constructor(
35
+ config: Partial<TranslationConfig> = {},
36
+ dirname: string | undefined = __dirname,
37
+ ) {
38
+ this.config = {
39
+ srcDir: config.srcDir || Pajo.join(dirname, 'src') || '',
40
+ supportedLocales: config.supportedLocales || ['en', 'fr'],
41
+ outputFile: config.outputFile || Pajo.join(dirname, 'src\\auto-intl.ts') || '',
42
+ modulesDir: config.modulesDir || Pajo.join(dirname, 'src\\modules') || '',
43
+ localesDir: config.localesDir || Pajo.join(dirname, 'src\\locales') || ''
44
+ };
45
+ console.log(`--> this.config:: `, this.config);
46
+ }
47
+
48
+ public async generate(): Promise<void> {
49
+ console.log('🔍 Scanning source files for translation keys...');
50
+
51
+ const srcFiles = this.findSourceFiles(this.config.srcDir);
52
+
53
+ for (const file of srcFiles) {
54
+ await this.extractKeysFromFile(file);
55
+ }
56
+
57
+ await this.discoverModules();
58
+
59
+ await this.generateAutoIntlFile();
60
+
61
+ console.log(`✅ Generated ${this.config.outputFile} with ${this.keys.length} translation keys`);
62
+ console.log(`📦 Found modules: ${Array.from(this.modules).join(', ')}`);
63
+ }
64
+
65
+ private findSourceFiles(dir: string): string[] {
66
+ const files: string[] = [];
67
+
68
+ try {
69
+ const items = fs.readdirSync(dir, { withFileTypes: true });
70
+
71
+ for (const item of items) {
72
+ const fullPath = path.join(dir, item.name);
73
+
74
+ if (item.name === 'node_modules' ||
75
+ item.name === 'dist' ||
76
+ item.name === 'build' ||
77
+ fullPath === this.config.outputFile) {
78
+ continue;
79
+ }
80
+
81
+ if (item.isDirectory()) {
82
+
83
+ files.push(...this.findSourceFiles(fullPath));
84
+ } else if (item.isFile()) {
85
+
86
+ const ext = path.extname(item.name).toLowerCase();
87
+ if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
88
+ files.push(fullPath);
89
+ }
90
+ }
91
+ }
92
+ } catch (error) {
93
+ console.error(`Error scanning directory ${dir}:`, error);
94
+ }
95
+
96
+ return files;
97
+ }
98
+
99
+ private async extractKeysFromFile(filePath: string): Promise<void> {
100
+ try {
101
+ const content = fs.readFileSync(filePath, 'utf-8');
102
+ const sourceFile = ts.createSourceFile(
103
+ path.basename(filePath),
104
+ content,
105
+ ts.ScriptTarget.Latest,
106
+ true
107
+ );
108
+
109
+ this.visitNode(sourceFile, filePath);
110
+ } catch (error) {
111
+ console.error(`Error processing file ${filePath}:`, error);
112
+ }
113
+ }
114
+
115
+ private visitNode(node: ts.Node, filePath: string): void {
116
+
117
+ if (ts.isCallExpression(node)) {
118
+ const expression = node.expression;
119
+
120
+ if (ts.isIdentifier(expression) && expression.text === 't') {
121
+ this.extractTFunctionCall(node, filePath);
122
+ }
123
+
124
+ if (ts.isIdentifier(expression) && expression.text === 'useTranslation') {
125
+ this.extractUseTranslationHook(node, filePath);
126
+ }
127
+ }
128
+
129
+ ts.forEachChild(node, (child) => this.visitNode(child, filePath));
130
+ }
131
+
132
+ private extractTFunctionCall(node: ts.CallExpression, filePath: string): void {
133
+ try {
134
+ const args = node.arguments;
135
+ if (args.length === 0) return;
136
+
137
+ const keyArg = args[0];
138
+ let key = '';
139
+
140
+ if (ts.isStringLiteral(keyArg)) {
141
+ key = keyArg.text;
142
+ } else if (ts.isTemplateLiteral(keyArg)) {
143
+
144
+ const text = keyArg.getText();
145
+ const templateParts = text.match(/`([^`]*)`/);
146
+ if (templateParts && templateParts[1]) {
147
+ key = templateParts[1].split('${')[0].trim();
148
+ }
149
+ } else if (ts.isNoSubstitutionTemplateLiteral(keyArg)) {
150
+ key = keyArg.text;
151
+ }
152
+
153
+ if (!key) return;
154
+
155
+ const translationKey: TranslationKey = { key };
156
+
157
+ // Extract module name from options (third argument)
158
+ if (args.length >= 3 && ts.isObjectLiteralExpression(args[2])) {
159
+ args[2].properties.forEach(property => {
160
+ if (ts.isPropertyAssignment(property)) {
161
+ const propertyName = property.name.getText();
162
+
163
+ if (propertyName === 'moduleName' && ts.isStringLiteral(property.initializer)) {
164
+ translationKey.module = property.initializer.text;
165
+ } else if (propertyName === 'defaultValue' && ts.isStringLiteral(property.initializer)) {
166
+ translationKey.defaultValue = property.initializer.text;
167
+ } else if (propertyName === 'context' && ts.isStringLiteral(property.initializer)) {
168
+ translationKey.context = property.initializer.text;
169
+ } else if (propertyName === 'count') {
170
+ translationKey.count = true;
171
+ }
172
+ }
173
+ });
174
+ }
175
+
176
+ // Try to infer module from file path
177
+ if (!translationKey.module) {
178
+ const moduleMatch = filePath.match(/modules[\\\/]([^\\\/]+)/);
179
+ if (moduleMatch) {
180
+ translationKey.module = moduleMatch[1];
181
+ }
182
+ }
183
+
184
+ // Add to keys if not already present
185
+ const existingKey = this.keys.find(k =>
186
+ k.key === translationKey.key &&
187
+ k.module === translationKey.module
188
+ );
189
+
190
+ if (!existingKey) {
191
+ this.keys.push(translationKey);
192
+ }
193
+ } catch (error) {
194
+ console.error(`Error extracting t() call in ${filePath}:`, error);
195
+ }
196
+ }
197
+
198
+ private extractUseTranslationHook(node: ts.CallExpression, filePath: string): void {
199
+ try {
200
+ const args = node.arguments;
201
+ if (args.length > 0 && ts.isStringLiteral(args[0])) {
202
+ const moduleName = args[0].text;
203
+
204
+ // Add to imported modules
205
+ this.importedModules.add(moduleName);
206
+
207
+ // Also add to modules set
208
+ this.modules.add(moduleName);
209
+ }
210
+ } catch (error) {
211
+ console.error(`Error extracting useTranslation hook in ${filePath}:`, error);
212
+ }
213
+ }
214
+
215
+ private async discoverModules(): Promise<void> {
216
+ try {
217
+ // Check if modules directory exists
218
+ if (!fs.existsSync(this.config.modulesDir)) {
219
+ return;
220
+ }
221
+
222
+ // List all modules
223
+ const moduleDirs = fs.readdirSync(this.config.modulesDir, { withFileTypes: true })
224
+ .filter(dirent => dirent.isDirectory())
225
+ .map(dirent => dirent.name);
226
+
227
+ // Check each module for locales directory
228
+ for (const moduleName of moduleDirs) {
229
+ const localesPath = path.join(this.config.modulesDir, moduleName, 'locales');
230
+
231
+ if (fs.existsSync(localesPath)) {
232
+ // Check if any locale file exists
233
+ const localeFiles = fs.readdirSync(localesPath)
234
+ .filter(file => file.endsWith('.json'))
235
+ .map(file => file.replace('.json', ''));
236
+
237
+ // Only add module if it has locale files
238
+ if (localeFiles.length > 0) {
239
+ this.modules.add(moduleName);
240
+ }
241
+ }
242
+ }
243
+ } catch (error) {
244
+ console.error('Error discovering modules:', error);
245
+ }
246
+ }
247
+
248
+ private async generateAutoIntlFile(): Promise<void> {
249
+ const outputDir = path.dirname(this.config.outputFile);
250
+
251
+ // Create output directory if it doesn't exist
252
+ if (!fs.existsSync(outputDir)) {
253
+ fs.mkdirSync(outputDir, { recursive: true });
254
+ }
255
+
256
+ const content = this.generateFileContent();
257
+ fs.writeFileSync(this.config.outputFile, content, 'utf-8');
258
+ }
259
+
260
+ private generateFileContent(): string {
261
+ const { supportedLocales } = this.config;
262
+
263
+ // Sort modules alphabetically
264
+ const sortedModules = Array.from(this.modules).sort();
265
+
266
+ // Check if base locales directory exists
267
+ const baseLocalesExist = fs.existsSync(this.config.localesDir);
268
+
269
+ // Generate base locales imports
270
+ const baseImports = supportedLocales.map(locale => {
271
+ const importPath = `./locales/${locale}.json`;
272
+ if (baseLocalesExist) {
273
+ return ` '${locale}': (() => import('${importPath}').then(module => module.default || module))`;
274
+ } else {
275
+ return ` '${locale}': (() => Promise.resolve({}))`;
276
+ }
277
+ }).join(',\n');
278
+
279
+ // Generate modules imports
280
+ const modulesImports = sortedModules.map(moduleName => {
281
+ const localesPath = path.join(this.config.modulesDir, moduleName, 'locales');
282
+ const moduleLocalesExist = fs.existsSync(localesPath);
283
+
284
+ const moduleLocales = supportedLocales.map(locale => {
285
+ const importPath = `./modules/${moduleName}/locales/${locale}.json`;
286
+ if (moduleLocalesExist) {
287
+ return ` '${locale}': (() => import('${importPath}').then(module => module.default || module))`;
288
+ } else {
289
+ return ` '${locale}': (() => Promise.resolve({}))`;
290
+ }
291
+ }).join(',\n');
292
+
293
+ return ` '${moduleName}': {\n${moduleLocales}\n }`;
294
+ }).join(',\n');
295
+
296
+ // Generate the TypeScript content
297
+ return `export const translations = {
298
+ 'base': {
299
+ ${baseImports}
300
+ },
301
+ 'modules': {
302
+ ${modulesImports.length > 0 ? modulesImports : ' // No modules with translations found'}
303
+ }
304
+ };
305
+
306
+ export default translations;`;
307
+ }
308
+
309
+ private generateTypeDefinitions(): string {
310
+ if (this.keys.length === 0) {
311
+ return 'string';
312
+ }
313
+
314
+ // Group keys by module
315
+ const keysByModule: Record<string, string[]> = {};
316
+
317
+ this.keys.forEach(key => {
318
+ const module = key.module || 'core';
319
+ if (!keysByModule[module]) {
320
+ keysByModule[module] = [];
321
+ }
322
+ keysByModule[module].push(key.key);
323
+ });
324
+
325
+ // Generate union types for each module
326
+ const moduleTypes = Object.entries(keysByModule).map(([module, keys]) => {
327
+ const keyUnion = keys
328
+ .filter((value, index, self) => self.indexOf(value) === index) // Remove duplicates
329
+ .map(k => `"${k}"`)
330
+ .join(' | ');
331
+
332
+ return module === 'core' ? keyUnion : `${module}:${keyUnion}`;
333
+ });
334
+
335
+ return moduleTypes.join(' | ');
336
+ }
337
+ }
338
+
339
+ async function TranslationInitiator(
340
+ dirname: string | undefined = __dirname
341
+ ) {
342
+ const config: Partial<TranslationConfig> = {};
343
+
344
+ const generator = new TranslationGenerator(config, dirname);
345
+
346
+ generator.generate().then(() => {
347
+ console.log('🎉 Translation generation completed successfully!');
348
+ }).catch((err) => {
349
+ console.error('❌ Failed to generate translations: ', err);
350
+ });
351
+ }
352
+
353
+ export default TranslationInitiator;
package/js/config.js ADDED
@@ -0,0 +1,110 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { Pajo } from '@jsarc/pajo';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ export class ConfigGenerator {
8
+ config;
9
+ modules = new Set();
10
+ constructor(config = {}, dirname = __dirname) {
11
+ this.config = {
12
+ srcDir: config.srcDir || Pajo.join(dirname, 'src') || '',
13
+ outputFile: config.outputFile || Pajo.join(dirname, 'src\\auto-config.ts') || '',
14
+ modulesDir: config.modulesDir || Pajo.join(dirname, 'src\\modules') || '',
15
+ rootConfigPath: config.rootConfigPath || Pajo.join(dirname, 'src\\config.json') || '',
16
+ projectRoot: config.projectRoot || dirname || ''
17
+ };
18
+ console.log(`--> ConfigGenerator config:: `, this.config);
19
+ }
20
+ async generate() {
21
+ console.log('🔍 Scanning for configuration files...');
22
+ await this.discoverModules();
23
+ await this.generateAutoConfigFile();
24
+ console.log(`✅ Generated ${this.config.outputFile}`);
25
+ console.log(`📦 Found modules with config: ${Array.from(this.modules).join(', ')}`);
26
+ }
27
+ async discoverModules() {
28
+ try {
29
+ if (!fs.existsSync(this.config.modulesDir)) {
30
+ console.log(`⚠️ Modules directory not found: ${this.config.modulesDir}`);
31
+ return;
32
+ }
33
+ const moduleDirs = fs.readdirSync(this.config.modulesDir, { withFileTypes: true })
34
+ .filter(dirent => dirent.isDirectory())
35
+ .map(dirent => dirent.name);
36
+ for (const moduleName of moduleDirs) {
37
+ const configPath = path.join(this.config.modulesDir, moduleName, 'config.json');
38
+ if (fs.existsSync(configPath)) {
39
+ this.modules.add(moduleName);
40
+ console.log(`📁 Found config for module: ${moduleName}`);
41
+ }
42
+ }
43
+ }
44
+ catch (error) {
45
+ console.error('Error discovering modules:', error);
46
+ }
47
+ }
48
+ async generateAutoConfigFile() {
49
+ const outputDir = path.dirname(this.config.outputFile);
50
+ if (!fs.existsSync(outputDir)) {
51
+ fs.mkdirSync(outputDir, { recursive: true });
52
+ }
53
+ const content = this.generateFileContent();
54
+ fs.writeFileSync(this.config.outputFile, content, 'utf-8');
55
+ }
56
+ getRelativeImportPath(targetPath) {
57
+ const outputDir = path.dirname(this.config.outputFile);
58
+ const relative = path.relative(outputDir, targetPath);
59
+ let importPath = relative.replace(/\\/g, '/');
60
+ if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
61
+ importPath = './' + importPath;
62
+ }
63
+ return importPath;
64
+ }
65
+ generateFileContent() {
66
+ const { rootConfigPath, modulesDir } = this.config;
67
+ const sortedModules = Array.from(this.modules).sort();
68
+ const rootConfigExists = fs.existsSync(rootConfigPath);
69
+ console.log(`📄 Root config exists: ${rootConfigExists} at ${rootConfigPath}`);
70
+ const baseImportPath = rootConfigExists
71
+ ? this.getRelativeImportPath(rootConfigPath)
72
+ : null;
73
+ const baseConfig = rootConfigExists && baseImportPath
74
+ ? ` 'app': (() => import('${baseImportPath}').then(module => module.default || module))`
75
+ : ` 'app': (() => Promise.resolve({}))`;
76
+ const modulesImports = sortedModules.map(moduleName => {
77
+ const configPath = path.join(modulesDir, moduleName, 'config.json');
78
+ const configExists = fs.existsSync(configPath);
79
+ if (configExists) {
80
+ const importPath = this.getRelativeImportPath(configPath);
81
+ console.log(`📄 Module ${moduleName} config: ${configPath} -> ${importPath}`);
82
+ return ` '${moduleName}': (() => import('${importPath}').then(module => module.default || module))`;
83
+ }
84
+ else {
85
+ console.log(`⚠️ Module ${moduleName} config not found: ${configPath}`);
86
+ return ` '${moduleName}': (() => Promise.resolve({}))`;
87
+ }
88
+ }).join(',\n');
89
+ return `export const configs = {
90
+ 'base': {
91
+ ${baseConfig}
92
+ },
93
+ 'modules': {
94
+ ${modulesImports.length > 0 ? modulesImports : ' // No modules with config.json found'}
95
+ }
96
+ };
97
+
98
+ export default configs;`;
99
+ }
100
+ }
101
+ async function ConfigInitiator(dirname = __dirname) {
102
+ const config = {};
103
+ const generator = new ConfigGenerator(config, dirname);
104
+ generator.generate().then(() => {
105
+ console.log('🎉 Config generation completed successfully!');
106
+ }).catch((err) => {
107
+ console.error('❌ Failed to generate config: ', err);
108
+ });
109
+ }
110
+ export default ConfigInitiator;
package/js/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import TranslationInitiator from './intl';
2
+ import ConfigInitiator from './config';
3
+ import RouteInitiator from './routing';
4
+ import ProviderInitiator from './provider';
5
+ export default function (dirname = __dirname) {
6
+ TranslationInitiator(dirname);
7
+ ConfigInitiator(dirname);
8
+ RouteInitiator(dirname);
9
+ ProviderInitiator(dirname);
10
+ }