import * as path from 'path' ; import * as fs from 'fs' ; import { through } from 'event-stream' ; import { ThroughStream } from 'through' ; import File = require ( 'vinyl' ) ; import * as Is from 'is' ; var util = require ( 'gulp-util' ) ; function log ( message , ... rest ) { util . log ( util . colors . green ( '[i18n]' ) , message , ... rest ) ; } interface Map < V > { [ key ] : V ; } interface LocalizeInfo { key : string ; comment : string [ ] ; } module LocalizeInfo { export function is ( value ) { let candidate = value as LocalizeInfo ; return Is . defined ( candidate ) && Is . string ( candidate . key ) && ( Is . undef ( candidate . comment ) || ( Is . array ( candidate . comment ) && candidate . comment . every ( element => Is . string ( element ) ) ) ) ; } } interface BundledFormat { keys : Map < ( string | LocalizeInfo ) [ ] > ; messages : Map < string [ ] > ; bundles : Map < string [ ] > ; } module BundledFormat { export function is ( value ) { if ( Is . undef ( value ) ) { return false ; } let candidate = value as BundledFormat ; let length = Object . keys ( value ) . length ; return length === 3 && Is . defined ( candidate . keys ) && Is . defined ( candidate . messages ) && Is . defined ( candidate . bundles ) ; } } const vscodeLanguages = [ 'chs' , 'cht' , 'jpn' , 'kor' , 'deu' , 'fra' , 'esn' , 'rus' , 'ita' ] ; const iso639_3_to_2 = { 'chs' : 'zh-cn' , 'cht' : 'zh-tw' , 'csy' : 'cs-cz' , 'deu' : 'de' , 'enu' : 'en' , 'esn' : 'es' , 'fra' : 'fr' , 'hun' : 'hu' , 'ita' : 'it' , 'jpn' : 'ja' , 'kor' : 'ko' , 'nld' : 'nl' , 'plk' : 'pl' , 'ptb' : 'pt-br' , 'ptg' : 'pt' , 'rus' : 'ru' , 'sve' : 'sv-se' , 'trk' : 'tr' } ; interface IDirectoryInfo { name : string ; iso639_2 : string ; } function sortLanguages ( directoryNames ) { return directoryNames . map ( ( dirName ) => { var lower = dirName . toLowerCase ( ) ; return { name : lower , iso639_2 : iso639_3_to_2 [ lower ] } ; } ) . sort ( ( a , b ) : number => { if ( ! a . iso639_2 && ! b . iso639_2 ) { return 0 ; } if ( ! a . iso639_2 ) { return - 1 ; } if ( ! b . iso639_2 ) { return 1 ; } return a . iso639_2 < b . iso639_2 ? - 1 : ( a . iso639_2 > b . iso639_2 ? 1 : 0 ) ; } ) ; } function stripComments ( content ) { /**
    * First capturing group matches double quoted string
    * Second matches single quotes string
    * Third matches block comments
    * Fourth matches line comments
    */ var regexp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g ; let result = content . replace ( regexp , ( match , m1 , m2 , m3 , m4 ) => { if ( m3 ) { return '' ; } else if ( m4 ) { let length = m4 . length ; if ( length > 2 && m4 [ length - 1 ] === '\n' ) { return m4 [ length - 2 ] === '\r' ? '\r\n' : '\n' ; } else { return '' ; } } else { return match ; } } ) ; return result ; } ; function escapeCharacters ( value ) { var result = [ ] ; for ( var i = 0 ; i < value . length ; i ++ ) { var ch = value . charAt ( i ) ; switch ( ch ) { case '\'' : result . push ( '\\\'' ) ; break ; case '"' : result . push ( '\\"' ) ; break ; case '\\' : result . push ( '\\\\' ) ; break ; case '\n' : result . push ( '\\n' ) ; break ; case '\r' : result . push ( '\\r' ) ; break ; case '\t' : result . push ( '\\t' ) ; break ; case '\b' : result . push ( '\\b' ) ; break ; case '\f' : result . push ( '\\f' ) ; break ; default : result . push ( ch ) ; } } return result . join ( '' ) ; } function processCoreBundleFormat ( fileHeader , json , emitter ) { let keysSection = json . keys ; let messageSection = json . messages ; let bundleSection = json . bundles ; let statistics = Object . create ( null ) ; let total = 0 ; let defaultMessages = Object . create ( null ) ; let modules = Object . keys ( keysSection ) ; modules . forEach ( ( module ) => { let keys = keysSection [ module ] ; let messages = messageSection [ module ] ; if ( ! messages || keys . length !== messages . length ) { emitter . emit ( 'error' , `Message for module ${ module } corrupted. Mismatch in number of keys and messages.` ) ; return ; } let messageMap = Object . create ( null ) ; defaultMessages [ module ] = messageMap ; keys . map ( ( key , i ) => { total ++ ; if ( Is . string ( key ) ) { messageMap [ key ] = messages [ i ] ; } else { messageMap [ key . key ] = messages [ i ] ; } } ) ; } ) ; let languageDirectory = path . join ( __dirname , '..' , '..' , 'i18n' ) ; let languages = sortLanguages ( fs . readdirSync ( languageDirectory ) . filter ( ( item ) => fs . statSync ( path . join ( languageDirectory , item ) ) . isDirectory ( ) ) ) ; languages . forEach ( ( language ) => { if ( ! language . iso639_2 ) { return ; } if ( process . env [ 'VSCODE_BUILD_VERBOSE' ] ) { log ( `Generating nls bundles for: ${ language . iso639_2 }` ) ; } statistics [ language . iso639_2 ] = 0 ; let localizedModules = Object . create ( null ) ; let cwd = path . join ( languageDirectory , language . name , 'src' ) ; modules . forEach ( ( module ) => { let order = keysSection [ module ] ; let i18nFile = path . join ( cwd , module ) + '.i18n.json' ; let messages = null ; if ( fs . existsSync ( i18nFile ) ) { let content = stripComments ( fs . readFileSync ( i18nFile , 'utf8' ) ) ; messages = JSON . parse ( content ) ; } else { if ( process . env [ 'VSCODE_BUILD_VERBOSE' ] ) { log ( `No localized messages found for module ${ module }. Using default messages.` ) ; } messages = defaultMessages [ module ] ; statistics [ language . iso639_2 ] = statistics [ language . iso639_2 ] + Object . keys ( messages ) . length ; } let localizedMessages = [ ] ; order . forEach ( ( keyInfo ) => { let key = null ; if ( Is . string ( keyInfo ) ) { key = keyInfo ; } else { key = keyInfo . key ; } let message = messages [ key ] ; if ( ! message ) { if ( process . env [ 'VSCODE_BUILD_VERBOSE' ] ) { log ( `No localized message found for key ${ key } in module ${ module }. Using default message.` ) ; } message = defaultMessages [ module ] [ key ] ; statistics [ language . iso639_2 ] = statistics [ language . iso639_2 ] + 1 ; } localizedMessages . push ( message ) ; } ) ; localizedModules [ module ] = localizedMessages ; } ) ; Object . keys ( bundleSection ) . forEach ( ( bundle ) => { let modules = bundleSection [ bundle ] ; let contents = [ fileHeader , `define("${ bundle }.nls.${ language . iso639_2 }", {` ] ; modules . forEach ( ( module , index ) => { contents . push ( `\t"${ module }": [` ) ; let messages = localizedModules [ module ] ; if ( ! messages ) { emitter . emit ( 'error' , `Didn't find messages for module ${ module }.` ) ; return ; } messages . forEach ( ( message , index ) => { contents . push ( `\t\t"${ escapeCharacters ( message ) }${ index < messages . length ? '",' : '"' }` ) ; } ) ; contents . push ( index < modules . length - 1 ? '\t],' : '\t]' ) ; } ) ; contents . push ( '});' ) ; emitter . emit ( 'data' , new File ( { path : bundle + '.nls.' + language . iso639_2 + '.js' , contents : new Buffer ( contents . join ( '\n' ) , 'utf-8' ) } ) ) ; } ) ; } ) ; Object . keys ( statistics ) . forEach ( key => { let value = statistics [ key ] ; log ( `${ key } has ${ value } untranslated strings.` ) ; } ) ; vscodeLanguages . forEach ( language => { let iso639_2 = iso639_3_to_2 [ language ] ; if ( ! iso639_2 ) { log ( `\tCouldn't find iso639 2 mapping for language ${ language }. Using default language instead.` ) ; } else { let stats = statistics [ iso639_2 ] ; if ( Is . undef ( stats ) ) { log ( `\tNo translations found for language ${ language }. Using default language instead.` ) ; } } } ) ; } export function processNlsFiles ( opts ) { return through ( function ( file ) { let fileName = path . basename ( file . path ) ; if ( fileName === 'nls.metadata.json' ) { let json = null ; if ( file . isBuffer ( ) ) { json = JSON . parse ( file . contents . toString ( 'utf8' ) ) ; } else { this . emit ( 'error' , `Failed to read component file: ${ file . relative }` ) ; } if ( BundledFormat . is ( json ) ) { processCoreBundleFormat ( opts . fileHeader , json , this ) ; } } this . emit ( 'data' , file ) ; } ) ; }