@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/README.md +524 -0
- package/config.ts +153 -0
- package/index.ts +23 -0
- package/intl.ts +366 -0
- package/js/config.js +110 -0
- package/js/index.js +17 -0
- package/js/intl.js +287 -0
- package/js/native-routing.js +696 -0
- package/js/provider.js +303 -0
- package/js/routing.js +533 -0
- package/native-routing.ts +871 -0
- package/package.json +21 -0
- package/provider.ts +403 -0
- package/routing.ts +691 -0
- package/tsconfig.json +27 -0
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;
|