@@ -12,6 +12,7 @@ import {
12
12
parseSync ,
13
13
transformAsync ,
14
14
transformFromAstSync ,
15
+ traverse ,
15
16
types ,
16
17
} from '@babel/core' ;
17
18
import templateBuilder from '@babel/template' ;
@@ -502,6 +503,8 @@ function createReplacePlugin(replacements: [string, string][]): PluginObj {
502
503
} ;
503
504
}
504
505
506
+ const USE_LOCALIZE_PLUGINS = false ;
507
+
505
508
async function createI18nPlugins (
506
509
locale : string ,
507
510
translation : unknown | undefined ,
@@ -565,6 +568,13 @@ export interface InlineOptions {
565
568
setLocale ?: boolean ;
566
569
}
567
570
571
+ interface LocalizePosition {
572
+ start : number ;
573
+ end : number ;
574
+ messageParts : TemplateStringsArray ;
575
+ expressions : types . Expression [ ] ;
576
+ }
577
+
568
578
const localizeName = '$localize' ;
569
579
570
580
export async function inlineLocales ( options : InlineOptions ) {
@@ -603,6 +613,10 @@ export async function inlineLocales(options: InlineOptions) {
603
613
throw new Error ( `Unknown error occurred inlining file "${ options . filename } "` ) ;
604
614
}
605
615
616
+ if ( ! USE_LOCALIZE_PLUGINS ) {
617
+ return inlineLocalesDirect ( ast , options ) ;
618
+ }
619
+
606
620
const diagnostics = [ ] ;
607
621
const inputMap = options . map && ( JSON . parse ( options . map ) as RawSourceMap ) ;
608
622
for ( const locale of i18n . inlineLocales ) {
@@ -667,6 +681,95 @@ export async function inlineLocales(options: InlineOptions) {
667
681
return { file : options . filename , diagnostics } ;
668
682
}
669
683
684
+ async function inlineLocalesDirect ( ast : ParseResult , options : InlineOptions ) {
685
+ if ( ! i18n || i18n . inlineLocales . size === 0 ) {
686
+ return { file : options . filename , diagnostics : [ ] , count : 0 } ;
687
+ }
688
+
689
+ const { default : MagicString } = await import ( 'magic-string' ) ;
690
+ const { default : generate } = await import ( '@babel/generator' ) ;
691
+ const utils = await import (
692
+ // tslint:disable-next-line: trailing-comma no-implicit-dependencies
693
+ '@angular/localize/src/tools/src/translate/source_files/source_file_utils'
694
+ ) ;
695
+ // tslint:disable-next-line: no-implicit-dependencies
696
+ const localizeDiag = await import ( '@angular/localize/src/tools/src/diagnostics' ) ;
697
+
698
+ const diagnostics = new localizeDiag . Diagnostics ( ) ;
699
+
700
+ const positions = findLocalizePositions ( ast , options , utils ) ;
701
+ if ( positions . length === 0 && ! options . setLocale ) {
702
+ return inlineCopyOnly ( options ) ;
703
+ }
704
+
705
+ // tslint:disable-next-line: no-any
706
+ let content = new MagicString ( options . code , { filename : options . filename } as any ) ;
707
+ const inputMap = options . map && ( JSON . parse ( options . map ) as RawSourceMap ) ;
708
+ let contentClone ;
709
+ for ( const locale of i18n . inlineLocales ) {
710
+ const isSourceLocale = locale === i18n . sourceLocale ;
711
+ // tslint:disable-next-line: no-any
712
+ const translations : any = isSourceLocale ? { } : i18n . locales [ locale ] . translation || { } ;
713
+ for ( const position of positions ) {
714
+ const translated = utils . translate (
715
+ diagnostics ,
716
+ translations ,
717
+ position . messageParts ,
718
+ position . expressions ,
719
+ isSourceLocale ? 'ignore' : options . missingTranslation || 'warning' ,
720
+ ) ;
721
+
722
+ const expression = utils . buildLocalizeReplacement ( translated [ 0 ] , translated [ 1 ] ) ;
723
+ const { code } = generate ( expression ) ;
724
+
725
+ content . overwrite ( position . start , position . end , code ) ;
726
+ }
727
+
728
+ if ( options . setLocale ) {
729
+ const setLocaleText = `var $localize=Object.assign(void 0===$localize?{}:$localize,{locale:"${ locale } "});` ;
730
+ contentClone = content . clone ( ) ;
731
+ content . prepend ( setLocaleText ) ;
732
+
733
+ // If locale data is provided, load it and prepend to file
734
+ const localeDataPath = i18n . locales [ locale ] && i18n . locales [ locale ] . dataPath ;
735
+ if ( localeDataPath ) {
736
+ const localDataContent = await loadLocaleData ( localeDataPath , true ) ;
737
+ // The semicolon ensures that there is no syntax error between statements
738
+ content . prepend ( localDataContent + ';' ) ;
739
+ }
740
+ }
741
+
742
+ const output = content . toString ( ) ;
743
+ const outputPath = path . join (
744
+ options . outputPath ,
745
+ i18n . flatOutput ? '' : locale ,
746
+ options . filename ,
747
+ ) ;
748
+ fs . writeFileSync ( outputPath , output ) ;
749
+
750
+ if ( inputMap ) {
751
+ const contentMap = content . generateMap ( ) ;
752
+ const outputMap = mergeSourceMaps (
753
+ options . code ,
754
+ inputMap ,
755
+ output ,
756
+ contentMap ,
757
+ options . filename ,
758
+ options . code . length > FAST_SOURCEMAP_THRESHOLD ,
759
+ ) ;
760
+
761
+ fs . writeFileSync ( outputPath + '.map' , JSON . stringify ( outputMap ) ) ;
762
+ }
763
+
764
+ if ( contentClone ) {
765
+ content = contentClone ;
766
+ contentClone = undefined ;
767
+ }
768
+ }
769
+
770
+ return { file : options . filename , diagnostics : diagnostics . messages , count : positions . length } ;
771
+ }
772
+
670
773
function inlineCopyOnly ( options : InlineOptions ) {
671
774
if ( ! i18n ) {
672
775
throw new Error ( 'i18n options are missing' ) ;
@@ -687,6 +790,62 @@ function inlineCopyOnly(options: InlineOptions) {
687
790
return { file : options . filename , diagnostics : [ ] , count : 0 } ;
688
791
}
689
792
793
+ function findLocalizePositions (
794
+ ast : ParseResult ,
795
+ options : InlineOptions ,
796
+ // tslint:disable-next-line: no-implicit-dependencies
797
+ utils : typeof import ( '@angular/localize/src/tools/src/translate/source_files/source_file_utils' ) ,
798
+ ) : LocalizePosition [ ] {
799
+ const positions : LocalizePosition [ ] = [ ] ;
800
+ if ( options . es5 ) {
801
+ traverse ( ast , {
802
+ CallExpression ( path : NodePath < types . CallExpression > ) {
803
+ const callee = path . get ( 'callee' ) ;
804
+ if (
805
+ callee . isIdentifier ( ) &&
806
+ callee . node . name === localizeName &&
807
+ utils . isGlobalIdentifier ( callee )
808
+ ) {
809
+ const messageParts = utils . unwrapMessagePartsFromLocalizeCall ( path ) ;
810
+ const expressions = utils . unwrapSubstitutionsFromLocalizeCall ( path . node ) ;
811
+ positions . push ( {
812
+ // tslint:disable-next-line: no-non-null-assertion
813
+ start : path . node . start ! ,
814
+ // tslint:disable-next-line: no-non-null-assertion
815
+ end : path . node . end ! ,
816
+ messageParts,
817
+ expressions,
818
+ } ) ;
819
+ }
820
+ } ,
821
+ } ) ;
822
+ } else {
823
+ const traverseFast = ( ( types as unknown ) as {
824
+ traverseFast : ( node : types . Node , enter : ( node : types . Node ) => void ) => void ;
825
+ } ) . traverseFast ;
826
+
827
+ traverseFast ( ast , node => {
828
+ if (
829
+ node . type === 'TaggedTemplateExpression' &&
830
+ types . isIdentifier ( node . tag ) &&
831
+ node . tag . name === localizeName
832
+ ) {
833
+ const messageParts = utils . unwrapMessagePartsFromTemplateLiteral ( node . quasi . quasis ) ;
834
+ positions . push ( {
835
+ // tslint:disable-next-line: no-non-null-assertion
836
+ start : node . start ! ,
837
+ // tslint:disable-next-line: no-non-null-assertion
838
+ end : node . end ! ,
839
+ messageParts,
840
+ expressions : node . quasi . expressions ,
841
+ } ) ;
842
+ }
843
+ } ) ;
844
+ }
845
+
846
+ return positions ;
847
+ }
848
+
690
849
async function loadLocaleData ( path : string , optimize : boolean ) : Promise < string > {
691
850
// The path is validated during option processing before the build starts
692
851
const content = fs . readFileSync ( path , 'utf8' ) ;
0 commit comments