@@ -49,6 +49,17 @@ class CachingAdapterMemory implements ICachingAdapter {
4949 }
5050}
5151
52+ function ensureTemplateHasAllParams ( template , newTemplate ) {
53+ // string ensureTemplateHasAllParams("a {b} c {d}", "я {b} і {d} в") // true
54+ // string ensureTemplateHasAllParams("a {b} c {d}", "я і {d} в") // false
55+ // string ensureTemplateHasAllParams("a {b} c {d}", "я {bb} і {d} в") // false
56+ const existingParams = template . match ( / { [ ^ } ] + } / g) ;
57+ const newParams = newTemplate . match ( / { [ ^ } ] + } / g) ;
58+ const existingParamsSet = new Set ( existingParams ) ;
59+ const newParamsSet = new Set ( newParams ) ;
60+ return existingParamsSet . size === newParamsSet . size && [ ...existingParamsSet ] . every ( p => newParamsSet . has ( p ) ) ;
61+ }
62+
5263class AiTranslateError extends Error {
5364 constructor ( message : string ) {
5465 super ( message ) ;
@@ -216,19 +227,31 @@ export default class I18N extends AdminForthPlugin {
216227 // disable create allowedActions for translations
217228 resourceConfig . options . allowedActions . create = false ;
218229
230+ // add hook to validate user did not screw up with template params
231+ resourceConfig . hooks . edit . beforeSave . push ( async ( { updates, oldRecord } : { updates : any , oldRecord ?: any } ) : Promise < { ok : boolean , error ?: string } > => {
232+ for ( const lang of this . options . supportedLanguages ) {
233+ if ( lang === 'en' ) {
234+ continue ;
235+ }
236+ if ( updates [ this . trFieldNames [ lang ] ] ) { // if user set '', it will have '' in updates, then it is fine, we shoudl nto check it
237+ if ( ! ensureTemplateHasAllParams ( oldRecord [ this . enFieldName ] , updates [ this . trFieldNames [ lang ] ] ) ) {
238+ return { ok : false , error : `Template params mismatch for ${ updates [ this . enFieldName ] } . Template param names should be the same as in original string. E. g. 'Hello {name}', should be 'Hola {name}' and not 'Hola {nombre}'!` } ;
239+ }
240+ }
241+ }
242+ return { ok : true } ;
243+ } ) ;
219244
220245 // add hook on edit of any translation
221- resourceConfig . hooks . edit . afterSave . push ( async ( { record, oldRecord } : { record : any , oldRecord ?: any } ) : Promise < { ok : boolean , error ?: string } > => {
246+ resourceConfig . hooks . edit . afterSave . push ( async ( { updates, oldRecord } : { updates : any , oldRecord ?: any } ) : Promise < { ok : boolean , error ?: string } > => {
247+ console . log ( '🪲edit.afterSave' , JSON . stringify ( updates , null , 2 ) , '-----' , JSON . stringify ( oldRecord , null , 2 ) ) ;
222248 if ( oldRecord ) {
223249 // find lang which changed
224250 let langsChanged : LanguageCode [ ] = [ ] ;
225251 for ( const lang of this . options . supportedLanguages ) {
226252 if ( lang === 'en' ) {
227253 continue ;
228254 }
229- if ( record [ this . trFieldNames [ lang ] ] !== oldRecord [ this . trFieldNames [ lang ] ] ) {
230- langsChanged . push ( lang ) ;
231- }
232255 }
233256
234257 // clear frontend cache for all langsChanged
@@ -241,6 +264,7 @@ export default class I18N extends AdminForthPlugin {
241264 }
242265 // clear frontend cache for all lan
243266
267+
244268 return { ok : true } ;
245269 } ) ;
246270
@@ -319,11 +343,16 @@ export default class I18N extends AdminForthPlugin {
319343 if ( this . options . completeAdapter ) {
320344 resourceConfig . options . bulkActions . push (
321345 {
346+ id : 'translate_all' ,
322347 label : 'Translate selected' ,
323348 icon : 'flowbite:language-outline' ,
324349 // if optional `confirm` is provided, user will be asked to confirm action
325350 confirm : 'Are you sure you want to translate selected items?' ,
326351 state : 'selected' ,
352+ allowed : ( { resource, adminUser, selectedIds, allowedActions } ) => {
353+ console . log ( 'allowedActions' , JSON . stringify ( allowedActions ) ) ;
354+ return allowedActions . edit ;
355+ } ,
327356 action : async ( { selectedIds, tr } ) => {
328357 let translatedCount = 0 ;
329358 try {
@@ -460,6 +489,11 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
460489 strId : translation [ this . primaryKeyFieldName ] ,
461490 } ;
462491 }
492+ // make sure LLM did not screw up with template params
493+ if ( translation [ this . enFieldName ] . includes ( '{' ) && ! ensureTemplateHasAllParams ( translation [ this . enFieldName ] , translatedStr ) ) {
494+ console . warn ( `LLM Screwed up with template params mismatch for "${ translation [ this . enFieldName ] } "on language ${ lang } , it returned "${ translatedStr } "` ) ;
495+ continue ;
496+ }
463497 updateStrings [
464498 translation [ this . primaryKeyFieldName ]
465499 ] . updates [ this . trFieldNames [ lang ] ] = translatedStr ;
@@ -507,7 +541,7 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
507541 category : string ,
508542 strId : string ,
509543 translatedStr : string
510- } > = { } ;
544+ } > = { } ;
511545
512546
513547 const langsInvolved = new Set ( Object . keys ( needToTranslateByLang ) ) ;
@@ -535,7 +569,7 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
535569 Object . entries ( updateStrings ) . map (
536570 async ( [ _ , { updates, strId } ] : [ string , { updates : any , category : string , strId : string } ] ) => {
537571 // because this will translate all languages, we can set completedLangs to all languages
538- const futureCompletedFieldValue = this . fullCompleatedFieldValue ;
572+ const futureCompletedFieldValue = this . computeCompletedFieldValue ( updates ) ;
539573
540574 await this . adminforth . resource ( this . resourceConfig . resourceId ) . update ( strId , {
541575 ...updates ,
0 commit comments