Skip to content

Commit 0f483e0

Browse files
committed
feat(cli): made i18n command more robust
Signed-off-by: Partik <[email protected]>
1 parent 3925a3a commit 0f483e0

File tree

2 files changed

+3417
-4322
lines changed

2 files changed

+3417
-4322
lines changed

packages/cli/src/cli/i18n.ts

Lines changed: 113 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { bucketTypeSchema, I18nConfig, localeCodeSchema } from '@replexica/spec';
22
import { ReplexicaEngine } from '@replexica/sdk';
33
import { Command } from 'commander';
4-
import Z from 'zod';
4+
import Z, { any } from 'zod';
55
import _ from 'lodash';
66
import { getConfig } from '../utils/config';
77
import { getSettings } from '../utils/settings';
@@ -22,117 +22,127 @@ export default new Command()
2222
.option('--force', 'Ignore lockfile and process all keys')
2323
.option('--verbose', 'Show verbose output')
2424
.option('--api-key <api-key>', 'Explicitly set the API key to use')
25+
.option('--strict', 'Stop on first error')
2526
.action(async function (options) {
2627
const ora = Ora();
27-
28+
const results: any = [];
29+
const flags = parseFlags(options);
30+
2831
try {
2932
ora.start('Loading configuration...');
30-
const flags = parseFlags(options);
3133
const i18nConfig = getConfig();
3234
const settings = getSettings(flags.apiKey);
3335
ora.succeed('Configuration loaded');
36+
37+
try {
38+
ora.start('Validating localization configuration...');
39+
validateParams(i18nConfig, flags);
40+
ora.succeed('Localization configuration is valid');
41+
} catch (error:any) {
42+
handleWarning('Localization configuration validation failed', error, flags.strict, results);
43+
if (flags.strict) return;
44+
}
3445

35-
ora.start('Validating localization configuration...');
36-
validateParams(i18nConfig, flags);
37-
ora.succeed('Localization configuration is valid');
38-
39-
ora.start('Connecting to Replexica Localization Engine...');
40-
const auth = await validateAuth(settings);
41-
ora.succeed('Replexica Localization Engine connected');
42-
ora.succeed(`Authenticated as ${auth.email}`);
46+
try {
47+
ora.start('Connecting to Replexica Localization Engine...');
48+
const auth = await validateAuth(settings);
49+
ora.succeed(`Authenticated as ${auth.email}`);
50+
} catch (error:any) {
51+
handleWarning('Failed to connect to Replexica Localization Engine', error, flags.strict, results);
52+
if (flags.strict) return;
53+
}
4354

44-
let buckets = getBuckets(i18nConfig!);
45-
if (flags.bucket) {
46-
buckets = buckets.filter((bucket) => bucket.type === flags.bucket);
55+
let buckets:any = [];
56+
try {
57+
buckets = getBuckets(i18nConfig!);
58+
if (flags.bucket) {
59+
buckets = buckets.filter((bucket:any) => bucket.type === flags.bucket);
60+
}
61+
ora.succeed('Buckets retrieved');
62+
} catch (error:any) {
63+
handleWarning('Failed to retrieve buckets', error, flags.strict, results);
64+
if (flags.strict) return;
4765
}
4866

4967
const targetLocales = getTargetLocales(i18nConfig!, flags);
5068
const lockfileHelper = createLockfileHelper();
5169

5270
// Ensure the lockfile exists
53-
ora.start('Ensuring i18n.lock exists...');
54-
if (!lockfileHelper.isLockfileExists()) {
55-
ora.start('Creating i18n.lock...');
56-
for (const bucket of buckets) {
57-
for (const pathPattern of bucket.pathPatterns) {
58-
const bucketLoader = createBucketLoader(bucket.type, pathPattern);
59-
bucketLoader.setDefaultLocale(i18nConfig!.locale.source);
60-
61-
const sourceData = await bucketLoader.pull(i18nConfig!.locale.source);
62-
lockfileHelper.registerSourceData(pathPattern, sourceData);
71+
try {
72+
ora.start('Ensuring i18n.lock exists...');
73+
if (!lockfileHelper.isLockfileExists()) {
74+
ora.start('Creating i18n.lock...');
75+
for (const bucket of buckets) {
76+
for (const pathPattern of bucket.pathPatterns) {
77+
const bucketLoader = createBucketLoader(bucket.type, pathPattern);
78+
bucketLoader.setDefaultLocale(i18nConfig!.locale.source);
79+
80+
const sourceData = await bucketLoader.pull(i18nConfig!.locale.source);
81+
lockfileHelper.registerSourceData(pathPattern, sourceData);
82+
}
6383
}
84+
ora.succeed('i18n.lock created');
85+
} else {
86+
ora.succeed('i18n.lock loaded');
6487
}
65-
ora.succeed('i18n.lock created');
66-
} else {
67-
ora.succeed('i18n.lock loaded');
88+
} catch (error:any) {
89+
handleWarning('Failed to ensure i18n.lock existence', error, flags.strict, results);
90+
if (flags.strict) return;
6891
}
6992

70-
// Exit with error if frozen flag is provided and there are any updated keys
71-
if (flags.frozen) {
72-
for (const bucket of buckets) {
93+
// Process each bucket
94+
for (const bucket of buckets) {
95+
try {
96+
console.log();
97+
ora.info(`Processing bucket: ${bucket.type}`);
7398
for (const pathPattern of bucket.pathPatterns) {
99+
const bucketOra = Ora({ indent: 2 }).info(`Processing path: ${pathPattern}`);
74100
const bucketLoader = createBucketLoader(bucket.type, pathPattern);
75101
bucketLoader.setDefaultLocale(i18nConfig!.locale.source);
76102

77103
const sourceData = await bucketLoader.pull(i18nConfig!.locale.source);
78-
const updatedSourceData = lockfileHelper.extractUpdatedData(pathPattern, sourceData);
79-
if (Object.keys(updatedSourceData).length) {
80-
throw new ReplexicaCLIError({
81-
message: `Translations are not up to date. Run the command without the --frozen flag to update the translations, then try again.`,
82-
docUrl: "translationFailed"
83-
});
84-
}
85-
}
86-
}
87-
}
88-
// Process each bucket
89-
for (const bucket of buckets) {
90-
console.log();
91-
ora.info(`Processing bucket: ${bucket.type}`);
92-
for (const pathPattern of bucket.pathPatterns) {
93-
const bucketOra = Ora({ indent: 2 }).info(`Processing path: ${pathPattern}`);
94-
95-
const bucketLoader = createBucketLoader(bucket.type, pathPattern);
96-
bucketLoader.setDefaultLocale(i18nConfig!.locale.source);
97-
98-
const sourceData = await bucketLoader.pull(i18nConfig!.locale.source);
99-
const updatedSourceData = flags.force ? sourceData : lockfileHelper.extractUpdatedData(pathPattern, sourceData);
100-
101-
for (const targetLocale of targetLocales) {
102-
bucketOra.start(`[${i18nConfig!.locale.source} -> ${targetLocale}] AI localization in progress...`);
103-
104-
const targetData = await bucketLoader.pull(targetLocale);
105-
106-
const processableData = calculateDataDelta({ sourceData, updatedSourceData, targetData });
107-
if (flags.verbose) {
108-
bucketOra.info(JSON.stringify(processableData, null, 2));
109-
}
110-
111-
const localizationEngine = createLocalizationEngineConnection({
112-
apiKey: settings.auth.apiKey,
113-
apiUrl: settings.auth.apiUrl,
114-
});
115-
const processedTargetData = await localizationEngine.process({
116-
sourceLocale: i18nConfig!.locale.source,
117-
sourceData,
118-
processableData,
119-
targetLocale,
120-
targetData,
121-
}, (progress) => {
122-
bucketOra.text = `[${i18nConfig!.locale.source} -> ${targetLocale}] (${progress}%) AI localization in progress...`;
123-
});
124-
125-
if (flags.verbose) {
126-
bucketOra.info(JSON.stringify(processedTargetData, null, 2));
104+
const updatedSourceData = flags.force ? sourceData : lockfileHelper.extractUpdatedData(pathPattern, sourceData);
105+
106+
for (const targetLocale of targetLocales) {
107+
try {
108+
bucketOra.start(`[${i18nConfig!.locale.source} -> ${targetLocale}] AI localization in progress...`);
109+
110+
const targetData = await bucketLoader.pull(targetLocale);
111+
const processableData = calculateDataDelta({ sourceData, updatedSourceData, targetData });
112+
if (flags.verbose) {
113+
bucketOra.info(JSON.stringify(processableData, null, 2));
114+
}
115+
116+
const localizationEngine = createLocalizationEngineConnection({
117+
apiKey: settings.auth.apiKey,
118+
apiUrl: settings.auth.apiUrl,
119+
});
120+
const processedTargetData = await localizationEngine.process({
121+
sourceLocale: i18nConfig!.locale.source,
122+
sourceData,
123+
processableData,
124+
targetLocale,
125+
targetData,
126+
}, (progress) => {
127+
bucketOra.text = `[${i18nConfig!.locale.source} -> ${targetLocale}] (${progress}%) AI localization in progress...`;
128+
});
129+
130+
if (flags.verbose) {
131+
bucketOra.info(JSON.stringify(processedTargetData, null, 2));
132+
}
133+
const finalTargetData = _.merge({}, targetData, processedTargetData);
134+
await bucketLoader.push(targetLocale, finalTargetData);
135+
bucketOra.succeed(`[${i18nConfig!.locale.source} -> ${targetLocale}] AI localization completed`);
136+
} catch (error:any) {
137+
handleWarning(`Failed to localize for ${targetLocale}`, error, flags.strict, results);
138+
if (flags.strict) return;
139+
}
127140
}
128-
const finalTargetData = _.merge({}, targetData, processedTargetData);
129-
130-
await bucketLoader.push(targetLocale, finalTargetData);
131-
132-
bucketOra.succeed(`[${i18nConfig!.locale.source} -> ${targetLocale}] AI localization completed`);
141+
lockfileHelper.registerSourceData(pathPattern, sourceData);
133142
}
134-
135-
lockfileHelper.registerSourceData(pathPattern, sourceData);
143+
} catch (error:any) {
144+
handleWarning(`Failed to process bucket: ${bucket.type}`, error, flags.strict, results);
145+
if (flags.strict) return;
136146
}
137147
}
138148

@@ -141,10 +151,26 @@ export default new Command()
141151
} catch (error: any) {
142152
ora.fail(error.message);
143153
process.exit(1);
154+
} finally {
155+
displaySummary(results);
144156
}
145157
});
146158

147159

160+
function handleWarning(step: string, error: Error, strictMode: boolean| undefined, results: any[]) {
161+
console.warn(`[WARNING] ${step}: ${error.message}`);
162+
results.push({ step, status: "Failed", error: error.message });
163+
if (strictMode) throw error;
164+
}
165+
166+
function displaySummary(results: any[]) {
167+
console.log("\nProcess Summary:");
168+
results.forEach((result) => {
169+
console.log(`${result.step}: ${result.status}`);
170+
if (result.error) console.log(` - Error: ${result.error}`);
171+
});
172+
}
173+
148174
function calculateDataDelta(args: {
149175
sourceData: Record<string, any>;
150176
updatedSourceData: Record<string, any>;
@@ -210,6 +236,7 @@ function parseFlags(options: any) {
210236
force: Z.boolean().optional(),
211237
frozen: Z.boolean().optional(),
212238
verbose: Z.boolean().optional(),
239+
strict: Z.boolean().optional(),
213240
}).parse(options);
214241
}
215242

0 commit comments

Comments
 (0)