fileoverview author Yosuke Ota
  import type { AST as VAST } from 'vue-eslint-parser' ; import type { RuleContext , I18nLocaleMessageDictionary , I18nLocaleMessageValue , LocaleKeyType } from '../types' ; import { extname } from 'path' ; import fs from 'fs' ; import { parseYamlValuesInI18nBlock , parseJsonValuesInI18nBlock } from './parsers' ; import { ResourceLoader } from './resource-loader' ; import JSON5 from 'json5' ; import yaml from 'js-yaml' ; import { joinPath , parsePath } from './key-path' ; const DEFAULT_LOCALE_PATTERN = '[A-Za-z0-9-_]+' ; const DEFAULT_LOCALE_FIELNAME_REGEX = new RegExp ( `(${ DEFAULT_LOCALE_PATTERN })\.` , 'i' ) ; const DEFAULT_LOCALE_CAPTURE_REGEX = new RegExp ( `^.*\/(?<locale>${ DEFAULT_LOCALE_PATTERN })\.(json5?|ya?ml)$` , 'i' ) ; /**
 * The localization message class
 */ export abstract class LocaleMessage { public readonly fullpath : string ; public readonly localeKey : LocaleKeyType ; public readonly file : string ; public readonly localePattern : RegExp ; private _locales : string [ ] | undefined ; param arg param { string } arg . fullpath param { string [ ] } arg . locales param { LocaleKeyType } arg . localeKey param { RegExp } args . localePattern constructor ( { fullpath , locales , localeKey , localePattern } ) { this . fullpath = fullpath ; type { LocaleKeyType } this . localeKey = localeKey ; type { string } this . file = fullpath . replace ( /^.*(\\|\/|:)/ , '' ) ; this . localePattern = this . getLocalePatternWithRegex ( localePattern ) ; this . _locales = locales ; } protected abstract getMessagesInternal ( ) ; /**
     * Get locale pattern with regular expression
     */ getLocalePatternWithRegex ( localePattern ? ) { return localePattern != null ? typeof localePattern === 'string' ? new RegExp ( localePattern , 'i' ) : Object . prototype . toString . call ( localePattern ) === '[object RegExp]' ? localePattern : DEFAULT_LOCALE_CAPTURE_REGEX : DEFAULT_LOCALE_CAPTURE_REGEX ; } returns { object } get messages ( ) : I18nLocaleMessageDictionary { return this . getMessagesInternal ( ) ; } returns { string [ ] } get locales ( ) : string [ ] { if ( this . _locales ) { return this . _locales ; } if ( this . localeKey === 'file' ) { const matched = this . file . match ( DEFAULT_LOCALE_FIELNAME_REGEX ) ; return ( this . _locales = [ ( matched && matched [ 1 ] ) || this . file ] ) ; } else if ( this . localeKey === 'path' ) { const matched = this . fullpath . match ( this . localePattern ) ; return ( this . _locales = [ ( matched && matched . groups ?. locale ) || this . fullpath ] ) ; } else if ( this . localeKey === 'key' ) { return ( this . _locales = Object . keys ( this . messages ) ) ; } return ( this . _locales = [ ] ) ; } isResolvedLocaleByFileName ( ) { return this . localeKey === 'file' || this . localeKey === 'path' ; } /**
     * Gets messages for the given locale.
     */ getMessagesFromLocale ( locale ) { if ( this . isResolvedLocaleByFileName ( ) ) { if ( ! this . locales . includes ( locale ) ) { return { } ; } return this . messages ; } if ( this . localeKey === 'key' ) { return ( this . messages [ locale ] || { } ) as I18nLocaleMessageDictionary ; } return { } ; } } export class BlockLocaleMessage extends LocaleMessage { public readonly block : VAST . VElement ; private lang : string ; private _messages : I18nLocaleMessageDictionary | null = null ; param arg param { VElement } arg . block param { string } arg . fullpath param { string [ ] } arg . locales param { LocaleKeyType } arg . localeKey param { RuleContext } arg . context param { string } arg . lang returns { LocaleMessage } constructor ( { block , fullpath , locales , localeKey , lang = 'json' } ) { super ( { fullpath , locales , localeKey } ) ; this . block = block ; this . lang = lang || 'json' ; } getMessagesInternal ( ) { if ( this . _messages ) { return this . _messages ; } const { lang } = this ; if ( lang === 'json' || lang === 'json5' ) { this . _messages = parseJsonValuesInI18nBlock ( this . block ) || { } ; } else if ( lang === 'yaml' || lang === 'yml' ) { this . _messages = parseYamlValuesInI18nBlock ( this . block ) || { } ; } else { this . _messages = { } ; } return this . _messages ; } } export class FileLocaleMessage extends LocaleMessage { private _resource : ResourceLoader < I18nLocaleMessageDictionary > ; param arg param { string } arg . fullpath param { string [ ] } arg . locales param { LocaleKeyType } arg . localeKey param { string | RegExp } args . localePattern constructor ( { fullpath , locales , localeKey , localePattern } ) { super ( { fullpath , locales , localeKey , localePattern } ) ; this . _resource = new ResourceLoader ( fullpath , fileName => { const ext = extname ( fileName ) . toLowerCase ( ) ; if ( ext === '.js' ) { const key = require . resolve ( fileName ) ; delete require . cache [ key ] ; return require ( fileName ) ; } else if ( ext === '.yaml' || ext === '.yml' ) { return yaml . load ( fs . readFileSync ( fileName , 'utf8' ) ) ; } else if ( ext === '.json' || ext === '.json5' ) { return JSON5 . parse ( fs . readFileSync ( fileName , 'utf8' ) ) ; } } ) ; } getMessagesInternal ( ) { return this . _resource . getResource ( ) ; } } /**
 * The localization messages class
 */ export class LocaleMessages { public readonly localeMessages : LocaleMessage [ ] ; param { LocaleMessage [ ] } localeMessages constructor ( localeMessages ) { this . localeMessages = localeMessages ; } get locales ( ) : string [ ] { const locales = new Set < string > ( ) ; for ( const localeMessage of this . localeMessages ) { for ( const locale of localeMessage . locales ) { locales . add ( locale ) ; } } return [ ... locales ] ; } /**
     * Checks whether it is empty.
     */ isEmpty ( ) { return this . localeMessages . length <= 0 ; } param { string } fullpath returns { LocaleMessage } findExistLocaleMessage ( fullpath ) { return ( this . localeMessages . find ( message => message . fullpath === fullpath ) || null ) ; } param { VElement } block returns { BlockLocaleMessage } findBlockLocaleMessage ( block ) { return ( this . localeMessages . find ( ( message ) : message is BlockLocaleMessage => ( message as BlockLocaleMessage ) . block === block ) || null ) ; } param { string } key findMissingPath ( key ) { let missingPath = [ ] ; for ( const locale of this . locales ) { const localeMessages = this . localeMessages . map ( lm => lm . getMessagesFromLocale ( locale ) ) ; if ( localeMessages . some ( last => { return last && typeof last !== 'string' ? last [ key ] != null : false ; } ) ) { return null ; } const paths = [ ... parsePath ( key ) ] ; let lasts = localeMessages ; const targetPaths = [ ] ; while ( paths . length ) { const path = paths . shift ( ) ! ; targetPaths . push ( path ) ; const values = lasts . map ( last => { return last && typeof last !== 'string' ? last [ path ] : undefined ; } ) . filter ( ( val ) : val is I18nLocaleMessageValue => val != null ) ; if ( values . length === 0 ) { if ( missingPath . length <= targetPaths . length ) { missingPath = targetPaths ; } break ; } lasts = values ; } if ( ! missingPath . length ) { return null ; } } return joinPath ( ... missingPath ) ; } }