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