import * as fs from 'fs' ; import * as path from 'path' ; import * as CSV from 'comma-separated-values' ; import { app , remote } from 'electron' ; param params declare function Locale ( /**
 * 若干个字符串，依次填充到 $1、$2
 */ ... params ) ; interface Locale { ( ... params ) : string ; renderAsText : ( htmlElement : HTMLElement , ... params : string [ ] ) => void ; renderAsHTML : ( htmlElement : HTMLElement , ... params : string [ ] ) => void ; } interface StringPack { [ key ] : string | StringPack ; } interface BindElement { locale : Locale ; htmlElement : HTMLElement ; type : 'text' | 'html' ; args : string [ ] ; } function readLangFile ( filePath ) { switch ( path . extname ( filePath ) ) { case '.js' : delete require . cache [ filePath ] ; return require ( filePath ) ; case '.json' : return JSON . parse ( fs . readFileSync ( filePath , { encoding : 'utf-8' } ) ) ; case '.csv' : const csv = CSV . parse ( fs . readFileSync ( filePath ) . toString ( ) , { cast : false } ) ; const localeObj = { } ; csv . forEach ( ( line ) => { localeObj [ line [ 0 ] ] = line [ 2 ] ; } ) ; return localeObj ; default : return { } ; } } function readLangDir ( dirPath ) { const lang = { } ; const files = fs . readdirSync ( dirPath ) ; const filesPath = files . map ( fileName => { return path . join ( dirPath , fileName ) ; } ) ; filesPath . forEach ( ( filePath , index ) => { const stat = fs . statSync ( filePath ) ; if ( stat . isDirectory ( ) ) { lang [ files [ index ] ] = readLangDir ( filePath ) ; } else { const fileName = files [ index ] ; lang [ path . basename ( fileName , path . extname ( fileName ) ) ] = readLangFile ( filePath ) ; } } ) ; return lang ; } class I18n { get text ( ) { return this . pText ; } get t ( ) { return this . text ; } get _ ( ) { return this . text ; } get locals ( ) { return this . pLocals ; } get actives ( ) : string [ ] { const copy = this . pActives . concat ( ) ; copy . pop ( ) ; return copy ; } set actives ( localTags ) { this . pActives = localTags . concat ( this . defaultLocale ) ; this . updateLocales ( ) ; } defaultLocale : string ; private pActives : string [ ] ; private pLocals : StringPack ; private pBindElementList : BindElement [ ] ; private pText : { [ key ] : { [ key ] : Locale ; } ; } ; constructor ( { directory = path . join ( __dirname , 'i18n' ) , actives = [ ] , defaultLocale = 'en-US' } ) { if ( ! fs . statSync ( directory ) . isDirectory ) { throw new Error ( 'param directory is not a directory' ) ; } if ( ! fs . readdirSync ( directory ) ) { throw new Error ( 'directory is empty, please make sure there is any files' ) ; } this . pLocals = readLangDir ( directory ) ; this . pBindElementList = [ ] ; this . defaultLocale = defaultLocale ; this . actives = actives ; ( ( ) => { param str param args const formatString = ( str , ... args ) => { for ( const index in args ) { if ( args [ index ] ) { str = str . replace ( `$${ index }` , args [ index ] ) ; } } return str ; } ; param chainsArray const createProxy = ( chainsArray ) => { return new Proxy ( ( ( ) => { const f = ( ... args ) => { for ( let i = 0 ; i < this . pActives . length ; i ++ ) { let localeObj = this . locals [ this . pActives [ i ] ] ; if ( ! localeObj ) { continue ; } for ( let j = 0 ; j < f . _chains . length ; j ++ ) { localeObj = localeObj [ f . _chains [ j ] ] ; if ( ! localeObj ) { break ; } else if ( j === f . _chains . length - 1 ) { return formatString ( localeObj as string , ... args ) ; } } } return 'MissingText' ; } ; f . _chains = chainsArray ; f . renderAsText = ( htmlElement , ... args ) => { this . bindElementText ( f , htmlElement , ... args ) ; } ; f . renderAsHTML = ( htmlElement , ... args ) => { this . bindElementHTML ( f , htmlElement , ... args ) ; } ; f . toString = ( ) => f . call ( this ) ; return f ; } ) ( ) , { get : ( target , key ) => { if ( ! target [ key ] ) { target [ key ] = createProxy ( target . _chains . concat ( key . toString ( ) ) ) ; } return target [ key ] ; } } ) ; } ; this . pText = new Proxy ( { } , { get : ( target , key ) => createProxy ( [ key ] ) } ) ; } ) ( ) ; } param Element unbindElement ( htmlElement ) { } bindElementText ( locale , htmlElement , ... args ) { return this . bindElement ( locale , htmlElement , 'text' , ... args ) ; } bindElementHTML ( locale , htmlElement , ... args ) { return this . bindElement ( locale , htmlElement , 'html' , ... args ) ; } parseAllElementsText ( htmlElement ) { return this . parseAllElements ( htmlElement , 'text' ) ; } parseAllElementsHTML ( htmlElement ) { return this . parseAllElements ( htmlElement , 'html' ) ; } private updateLocales ( ) { this . pBindElementList . forEach ( ( { locale , htmlElement , type , args } ) => { const text = locale ( ... args ) ; if ( type === 'text' ) { htmlElement . innerText = text ; } else { htmlElement . innerHTML = text ; } } ) ; } private bindElement ( locale , htmlElement , type , ... args ) { this . pBindElementList . push ( { locale , htmlElement , type , args } ) ; this . updateLocales ( ) ; } private parseAllElements ( htmlElement , type , ... args ) { const renderElement = ( element ) => { const i18nLocaleKeyChain = element . dataset . i18n . split ( '.' ) ; const i18nLocaleElement = this . text [ i18nLocaleKeyChain [ 0 ] ] [ i18nLocaleKeyChain [ 1 ] ] ; this . bindElement ( i18nLocaleElement , element , type , ... args ) ; } ; if ( htmlElement . getAttribute ( 'data-i18n' ) ) { renderElement ( htmlElement ) ; } htmlElement . querySelectorAll ( '[data-i18n]' ) . forEach ( renderElement ) ; } } export default new I18n ( { actives : [ ( app || remote . app ) . getLocale ( ) ] } ) ;