@softvisio/core
Version:
Softisio core
475 lines (356 loc) • 12.5 kB
JavaScript
import DigitalSize from "#lib/digital-size";
import Interval from "#lib/interval";
import isBrowser from "#lib/is-browser";
import { DEFAULT_CURRENCY, DEFAULT_LOCALE_ID, LANGUAGES, LOCALES, PLURAL_EXPRESSIONS } from "#lib/locale/constants";
import L10nt from "#lib/locale/l10nt";
import MsgId from "#lib/locale/msgid";
const dateTimeFormats = {},
numberFormats = {},
relativeTimeFormats = {},
digitalSizeFormats = {},
displayNameFormats = {},
digitalSizeFormatters = {},
durationFormats = {},
listFormats = {};
const relativeTimeFormatters = {},
displayNamesFormatters = {},
dateTimeFormatters = {},
currencyFormatters = {},
percentFormatters = {},
numberFormatters = {},
durationFormatters = {},
listFormatters = {};
class LocaleDomains {
#domains = new Map();
// public
has ( id ) {
return this.#domains.has( id );
}
get ( id ) {
return this.#domains.get( id );
}
add ( id, locale ) {
if ( typeof id === "object" ) {
for ( const [ domain, locale ] of Object.entries( id ) ) {
if ( !this.#domains.has( domain ) ) {
this.#domains.set( domain, Locale.new( locale ) );
}
else {
this.#domains.get( domain ).add( locale );
}
}
}
else {
if ( !this.#domains.has( id ) ) {
this.#domains.set( id, Locale.new( locale ) );
}
else {
this.#domains.get( id ).add( locale );
}
}
}
set ( id, locale ) {
if ( typeof id === "object" ) {
for ( const [ id, locale ] of Object.entries( id ) ) {
this.delete( id );
this.add( id, locale );
}
}
else {
this.delete( id );
this.add( id, locale );
}
}
delete ( id ) {
this.#domains.delete( id );
}
toJSON () {
if ( !this.#domains.size ) return;
const json = {};
for ( const [ id, locale ] of this.#domains.entries() ) json[ id ] = locale.toJSON();
return json;
}
}
export default class Locale extends Intl.Locale {
#name;
#languageName;
#pluralExpression;
#messages;
#currency;
#domains;
#hour12;
#groupSeparator;
#decimalSeparator;
constructor ( locale ) {
if ( !locale ) {
super( DEFAULT_LOCALE_ID );
}
else if ( typeof locale === "string" ) {
super( locale );
}
else if ( locale instanceof Locale ) {
super( locale.id || DEFAULT_LOCALE_ID );
this.#currency = locale.currency;
this.add( locale );
}
else if ( locale instanceof Intl.Locale ) {
super( locale.baseName );
}
else {
super( locale.id || DEFAULT_LOCALE_ID );
this.#currency = locale.currency;
this.add( locale );
}
this.#pluralExpression = PLURAL_EXPRESSIONS[ this.language ];
this.#currency ||= DEFAULT_CURRENCY;
}
// static
static get default () {
return DEFAULT_LOCALE;
}
static get defaultLocale () {
return DEFAULT_LOCALE_ID;
}
static get defaultCurrency () {
return DEFAULT_CURRENCY;
}
static get L10nt () {
return L10nt;
}
static new ( locale ) {
if ( locale instanceof this ) return locale;
return new this( locale );
}
static isValid ( locale ) {
return LOCALES.has( locale );
}
static languageisValid ( language ) {
return LANGUAGES.has( language );
}
// properties
get id () {
return this.baseName;
}
get isValid () {
return LOCALES.has( this.id );
}
get name () {
this.#name ??= this.formatName( this.id, "type:language,languageDisplay:standard,style:short" );
return this.#name;
}
get languageName () {
this.#languageName ??= this.id === this.language
? this.name
: new Locale( this.language ).name;
return this.#languageName;
}
get currency () {
return this.#currency;
}
get domains () {
this.#domains ??= new LocaleDomains();
return this.#domains;
}
get hour12 () {
if ( this.#hour12 === undefined ) this.#parseOptions();
return this.#hour12;
}
get groupSeparator () {
if ( this.#groupSeparator === undefined ) this.#parseOptions();
return this.#groupSeparator;
}
get decimalSeparator () {
if ( this.#decimalSeparator === undefined ) this.#parseOptions();
return this.#decimalSeparator;
}
// public
l10n ( singular, plural, pluralNumber ) {
var message, translation;
if ( plural ) {
if ( typeof pluralNumber !== "number" ) pluralNumber = 0;
if ( pluralNumber === 1 ) {
message = singular;
}
else {
message = plural;
}
translation = this.#messages?.[ singular ]?.[ plural ]?.[ this.#pluralExpression( pluralNumber ) ];
}
else {
message = singular;
translation = this.#messages?.[ singular ]?.[ "" ];
}
if ( message instanceof MsgId ) {
return message.translate( translation );
}
else {
return translation || message;
}
}
l10nt ( singular, plural, pluralNumber ) {
return new L10nt( this, singular, {
plural,
pluralNumber,
} );
}
add ( locale ) {
if ( !locale ) return;
if ( locale instanceof Locale ) locale = locale.toJSON();
if ( locale.messages ) {
this.#messages ??= {};
for ( const [ singular, translations ] of Object.entries( locale.messages ) ) {
this.#messages[ singular ] ??= {};
for ( const plural in translations ) {
this.#messages[ singular ][ plural ] = translations[ plural ];
}
}
}
if ( locale.domains ) this.domains.add( locale.domains );
}
toString () {
return this.id;
}
toJSON () {
return {
"id": this.id,
"currency": this.currency,
"messages": this.#messages,
"domains": this.#domains?.toJSON(),
};
}
// formatters
formatName ( name, format ) {
format ||= "";
displayNamesFormatters[ this.id ] ??= {};
return ( displayNamesFormatters[ this.id ][ format ] ??= new Intl.DisplayNames( this.id, this.parseFormat( format, displayNameFormats ) ) ).of( name );
}
formatDate ( value, format ) {
format ||= "";
dateTimeFormatters[ this.id ] ??= {};
return ( dateTimeFormatters[ this.id ][ format ] ??= new Intl.DateTimeFormat( this.id, this.parseFormat( format, dateTimeFormats ) ) ).format( value );
}
formatNumber ( value, format ) {
format ||= "";
numberFormatters[ this.id ] ??= {};
return ( numberFormatters[ this.id ][ format ] ??= new Intl.NumberFormat( this.id, this.parseFormat( format, numberFormats ) ) ).format( value );
}
formatPercent ( value, format ) {
format ||= "";
percentFormatters[ this.id ] = {};
var formatter = percentFormatters[ this.id ][ format ];
if ( !formatter ) {
const parsedFormat = {
...this.parseFormat( format, numberFormats ),
"style": "percent",
};
formatter = percentFormatters[ this.id ][ format ] = new Intl.NumberFormat( this.id, parsedFormat );
}
return formatter.format( value );
}
formatCurrency ( value, format ) {
format ||= "";
const formatterId = this.currency + "/" + format;
currencyFormatters[ this.id ] ??= {};
var formatter = currencyFormatters[ this.id ][ formatterId ];
if ( !formatter ) {
const parsedFormat = {
"currency": this.currency,
...this.parseFormat( format, numberFormats ),
"style": "currency",
};
formatter = currencyFormatters[ this.id ][ formatterId ] = new Intl.NumberFormat( this.id, parsedFormat );
}
return formatter.format( value );
}
formatRelativeDate ( interval, format ) {
format ||= "";
interval = Interval.new( interval );
relativeTimeFormatters[ this.id ] ??= {};
return ( relativeTimeFormatters[ this.id ][ format ] ??= new Intl.RelativeTimeFormat( this.id, this.parseFormat( format, relativeTimeFormats ) ) ).format( ...interval.getFormatRelativeDateParams() );
}
formatDigitalSize ( value, format ) {
format ||= "";
digitalSizeFormatters[ this.id ] = {};
var formatter = digitalSizeFormatters[ this.id ][ format ];
if ( !formatter ) {
const parsedFormat = {
"maximumFractionDigits": 1,
...this.parseFormat( format, digitalSizeFormats ),
"style": "unit",
};
if ( !parsedFormat.unit ) {
const params = DigitalSize.new( value ).getFormatDifitalSizeParam();
parsedFormat.unit = params.unit;
value = params.value;
formatter = new Intl.NumberFormat( this.id, parsedFormat );
}
else {
formatter = digitalSizeFormatters[ this.id ][ format ] = new Intl.NumberFormat( this.id, parsedFormat );
}
}
return formatter.format( value );
}
formatDuration ( interval, format ) {
format ||= "";
durationFormatters[ this.id ] ??= {};
const formatter = ( durationFormatters[ this.id ][ format ] ??= new Intl.DurationFormat( this.id, this.parseFormat( format, durationFormats ) ) );
if ( interval instanceof Temporal.Duration ) {
return formatter.format( interval );
}
else {
interval = Interval.new( interval );
return formatter.format( interval.getFormatDurationParams() );
}
}
formatList ( value, format ) {
format ||= "";
listFormatters[ this.id ] ??= {};
return ( listFormatters[ this.id ][ format ] ??= new Intl.ListFormat( this.id, this.parseFormat( format, listFormats ) ) ).format( value );
}
parseFormat ( format, cache ) {
if ( !format ) return {};
var parsedFormat = cache?.[ format ];
if ( !parsedFormat ) {
parsedFormat = {};
for ( const token of format.split( "," ) ) {
const idx = token.indexOf( ":" );
if ( idx < 1 ) continue;
parsedFormat[ token.slice( 0, idx ) ] = token.slice( idx + 1 );
}
// cache parsed format
if ( cache ) cache[ format ] = parsedFormat;
}
return parsedFormat;
}
// private
#parseOptions () {
const dateTimeOptions = new Intl.DateTimeFormat( this, { "dateStyle": "full", "timeStyle": "full" } ).resolvedOptions();
this.#hour12 = dateTimeOptions.hour12;
this.#groupSeparator = "";
this.#decimalSeparator = "";
const numberParts = new Intl.NumberFormat( this, { "useGrouping": true } ).formatToParts( 123_456_789.1 );
for ( const part of numberParts ) {
if ( part.type === "group" ) this.#groupSeparator = part.value;
else if ( part.type === "decimal" ) this.#decimalSeparator = part.value;
}
}
}
const DEFAULT_LOCALE = new Locale();
if ( !isBrowser ) {
if ( !globalThis.l10n ) {
Object.defineProperty( globalThis, "l10n", {
"configurable": false,
"writable": false,
"enumerable": true,
"value": DEFAULT_LOCALE.l10n.bind( DEFAULT_LOCALE ),
} );
}
if ( !globalThis.l10nt ) {
Object.defineProperty( globalThis, "l10nt", {
"configurable": false,
"writable": false,
"enumerable": true,
"value": DEFAULT_LOCALE.l10nt.bind( DEFAULT_LOCALE ),
} );
}
}