import { merge , set } from 'lodash-es' ; import Decimal from 'decimal.js' ; import { FSTranslationKeys , I18n , NumberLike , TranslationKey , TranslationKeys , Translations } from './types' ; const MISSING_TRANSLATIONS_ERROR = new Error ( 'You must provide translations before translating.' ) ; const MISSING_CURRENCY_ERROR = new Error ( 'You must provide a currency' ) ; const INVALID_NUMBER_ERROR = new Error ( `Provided argument is not a valid number` ) ; const INVALID_DATE_ERROR = new Error ( `Provided argument is not a valid date` ) ; export default class I18nHelper { protected readonly i18n : I18n ; protected localeListeners : ( ( locale : string ) => void ) [ ] ; constructor ( i18n ) { this . i18n = i18n ; this . i18n . fallbacks = true ; this . localeListeners = [ ] ; } param { Translations } translations returns { object } public addTranslations < T = TranslationKeys > ( translations ) { if ( ! this . i18n . translations ) { this . i18n . translations = translations ; } else { merge ( this . i18n . translations , translations ) ; } return this . getAvailableStringTranslations ( ) ; } param { string } locale public setLocale ( locale ) { this . i18n . locale = locale ; this . localeListeners . forEach ( ( func ) => { func ( locale ) ; } ) ; } public addLocaleListener ( func ) { this . localeListeners . push ( func ) ; } public removeLocaleListener ( func ) { this . localeListeners = this . localeListeners . filter ( ( testFunc ) => { return func !== testFunc ; } ) ; } returns { Translations } public getAvailableStringTranslations < T = TranslationKeys > ( ) { if ( this . i18n . translations === undefined ) { throw MISSING_TRANSLATIONS_ERROR ; } const translations = this . i18n . translations ; const availableTranslations = Object . keys ( translations ) . map ( locale => this . flatten ( translations [ locale ] ) ) ; return merge ( { } , ... availableTranslations ) ; } example param { string | number | Decimal } num param { Intl . NumberFormatOptions } options returns { string } public number ( num , options ? ) { if ( typeof num === 'string' || num instanceof Decimal ) { num = this . convertToNumber ( num ) ; } if ( typeof num !== 'number' || isNaN ( num ) ) { throw INVALID_NUMBER_ERROR ; } return num . toLocaleString ( this . currentLocale ( ) , options ) ; } example param { string | number | Decimal | CurrencyValue } num param { string } currency param { Intl . NumberFormatOptions } options returns { string } public currency ( num , currency ? , options ? ) { if ( ! currency && ( ! options || ! options . currency ) && ( typeof num !== 'object' || num instanceof Decimal || ! num . currencyCode ) ) { throw MISSING_CURRENCY_ERROR ; } if ( typeof num === 'object' && ! ( num instanceof Decimal ) ) { currency = num . currencyCode ; num = this . convertToNumber ( num . value ) ; } const currencyOptions = { style : 'currency' , currency , ... options } ; return this . number ( num , currencyOptions ) ; } example param { string | number | Decimal | CurrencyValue } num param { Intl . NumberFormatOptions } options returns { string } public percent ( num , options ? ) { const percentOptions = { style : 'percent' , ... options } ; return this . number ( num , percentOptions ) ; } example param { string | number | Decimal | CurrencyValue } date param { Intl . DateTimeFormatOptions } options returns { string } public date ( date , options ? ) { if ( ! ( date instanceof Date ) || isNaN ( date . valueOf ( ) ) ) { throw INVALID_DATE_ERROR ; } return date . toLocaleDateString ( this . currentLocale ( ) , options ) ; } param { string | string [ ] } translationKey param { I18n . TranslationOptions } options returns { string } public string ( translationKey ? , options ? ) { if ( Object . getOwnPropertyNames ( this . i18n . translations ) . length === 0 ) { throw MISSING_TRANSLATIONS_ERROR ; } if ( translationKey === undefined ) { console . warn ( 'translationKey is undefined' ) ; return '' ; } return this . i18n . translate ( translationKey , options ) ; } returns { string } public currentLocale ( ) { return this . i18n . currentLocale ( ) ; } param { NumberLike } num returns { number } protected convertToNumber ( num ) { if ( num instanceof Decimal ) { return num . toNumber ( ) ; } else if ( typeof num === 'string' ) { return parseFloat ( num ) ; } return num ; } param { TranslationKeys } translations param { TranslationKeys } parentTranslations param { string [ ] } paths returns { TranslationKeys } protected flatten ( translations , parentTranslations = { } , paths = [ ] ) { return Object . keys ( translations ) . reduce ( ( parentTranslations , key ) => { paths . push ( key ) ; if ( ! this . isTranslationKey ( translations [ key ] ) ) { this . flatten ( translations [ key ] as TranslationKeys , parentTranslations , paths ) ; } else { set ( parentTranslations , paths , paths . join ( '.' ) ) ; } paths . pop ( ) ; return parentTranslations ; } , parentTranslations ) ; } param { string | object } translationKey returns { boolean } protected isTranslationKey ( translationKey ) { const isString = typeof translationKey === 'string' ; const hasPluralizationKeys = translationKey && typeof translationKey === 'object' && ( typeof translationKey . zero === 'string' || typeof translationKey . one === 'string' || typeof translationKey . other === 'string' ) ; return isString || hasPluralizationKeys ; } }