11import { bucketTypeSchema , I18nConfig , localeCodeSchema } from '@replexica/spec' ;
22import { ReplexicaEngine } from '@replexica/sdk' ;
33import { Command } from 'commander' ;
4- import Z from 'zod' ;
4+ import Z , { any } from 'zod' ;
55import _ from 'lodash' ;
66import { getConfig } from '../utils/config' ;
77import { 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+
148174function 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