-
-
Notifications
You must be signed in to change notification settings - Fork 213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: AOT compilation with icu-to-json
(experiment)
#705
base: main
Are you sure you want to change the base?
Changes from 7 commits
ee8279c
7997a50
0f25794
e89de9c
2dc39b5
3fa4253
44337ea
69bfe0c
1d44910
7afec20
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import type {compile} from 'icu-to-json/compiler'; | ||
|
||
type MessageFormat = Omit< | ||
ReturnType<typeof compile>, | ||
// TODO: Do we need the args? | ||
'args' | ||
>; | ||
|
||
export default MessageFormat; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,9 @@ | ||
// eslint-disable-next-line import/no-named-as-default -- False positive | ||
import type IntlMessageFormat from 'intl-messageformat'; | ||
import MessageFormat from './MessageFormat'; | ||
|
||
type MessageFormatCache = Map< | ||
/** Format: `${locale}.${namespace}.${key}.${message}` */ | ||
string, | ||
IntlMessageFormat | ||
string, // Could simplify the key here | ||
MessageFormat | ||
>; | ||
|
||
export default MessageFormatCache; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import Formats from './Formats'; | ||
|
||
// Copied from intl-messageformat | ||
const defaults = { | ||
number: { | ||
integer: {maximumFractionDigits: 0}, | ||
currency: {style: 'currency'}, | ||
percent: {style: 'percent'} | ||
}, | ||
date: { | ||
short: {month: 'numeric', day: 'numeric', year: '2-digit'}, | ||
medium: {month: 'short', day: 'numeric', year: 'numeric'}, | ||
long: {month: 'long', day: 'numeric', year: 'numeric'}, | ||
full: {weekday: 'long', month: 'long', day: 'numeric', year: 'numeric'} | ||
}, | ||
time: { | ||
short: {hour: 'numeric', minute: 'numeric'}, | ||
medium: {hour: 'numeric', minute: 'numeric', second: 'numeric'}, | ||
long: { | ||
hour: 'numeric', | ||
minute: 'numeric', | ||
second: 'numeric', | ||
timeZoneName: 'short' | ||
}, | ||
full: { | ||
hour: 'numeric', | ||
minute: 'numeric', | ||
second: 'numeric', | ||
timeZoneName: 'short' | ||
} | ||
} | ||
} as const; | ||
|
||
type FormatNameOrArgs<Options> = | ||
| string | ||
| { | ||
type: number; // TODO: Unused, is this necessary? | ||
tokens: Array<unknown>; // TODO: Unused, is this necessary? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could these two properties be removed from the parsed result? Or should they be used by the consumer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what you mean - Formatters are called here with the format options are passed through from MessageFormat There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean e.g. these cases: // [["price",4,"numberFmt",{"type":0,"tokens":[{"stem":"currency","options":["EUR"]}],"parsedOptions":{"style":"currency","currency":"EUR"}}]]
'{price, number, ::currency/EUR}'
// [["value",4,"numberFmt",{"type":0,"tokens":[{"stem":".#","options":[]}],"parsedOptions":{"maximumFractionDigits":1}}]]
'{value, number, ::.#}' Is [["value",4,"numberFmt",{"maximumFractionDigits":1}]] Similarly, date skeletons seem to be a bit verbose: // [["now",4,"date",{"type":1,"pattern":"yyyyMdHm","parsedOptions":{"year":"numeric","month":"numeric","day":"numeric","hourCycle":"h23","hour":"numeric","minute":"numeric"}}]]
'{now, date, ::yyyyMdHm}' Could the |
||
parsedOptions?: Options; | ||
}; | ||
|
||
export default function getFormatters( | ||
timeZone?: string, | ||
formats?: Partial<Formats>, | ||
globalFormats?: Partial<Formats> | ||
) { | ||
const formatters = { | ||
date( | ||
value: number | string, | ||
locale: string, | ||
formatNameOrArgs?: FormatNameOrArgs<Intl.DateTimeFormatOptions> | ||
) { | ||
const allFormats = { | ||
...defaults.date, | ||
// TODO: time & date vs dateTime. Maybe we should separate | ||
// time and date, because ICU does this too? | ||
...globalFormats?.dateTime | ||
}; | ||
|
||
const options: Intl.DateTimeFormatOptions = {timeZone}; | ||
if (formatNameOrArgs) { | ||
if (typeof formatNameOrArgs === 'string') { | ||
if (formatNameOrArgs in allFormats) { | ||
Object.assign(options, (allFormats as any)[formatNameOrArgs]); | ||
} | ||
} | ||
if (typeof formatNameOrArgs === 'object') { | ||
Object.assign(options, formatNameOrArgs.parsedOptions); | ||
} | ||
} | ||
|
||
// TODO: Use Intl.DateTimeFormat and caching? | ||
return new Date(value).toLocaleDateString(locale, options); | ||
}, | ||
|
||
time( | ||
value: number | string, | ||
locale: string, | ||
formatNameOrArgs?: FormatNameOrArgs<Intl.DateTimeFormatOptions> | ||
) { | ||
const allFormats = { | ||
...defaults.time, | ||
...globalFormats?.dateTime | ||
}; | ||
|
||
const options: Intl.DateTimeFormatOptions = {timeZone}; | ||
if (formatNameOrArgs) { | ||
if (typeof formatNameOrArgs === 'string') { | ||
if (formatNameOrArgs in allFormats) { | ||
Object.assign(options, (allFormats as any)[formatNameOrArgs]); | ||
} | ||
} | ||
if (typeof formatNameOrArgs === 'object') { | ||
Object.assign(options, formatNameOrArgs.parsedOptions); | ||
} | ||
} | ||
|
||
// TODO: Use Intl.DateTimeFormat and caching? | ||
return new Date(value).toLocaleTimeString(locale, options); | ||
}, | ||
|
||
numberFmt( | ||
value: number, | ||
locale: string, | ||
formatNameOrArgs?: FormatNameOrArgs<Intl.NumberFormatOptions> | ||
) { | ||
const allFormats = { | ||
...defaults.number, | ||
...globalFormats?.number, | ||
...formats?.number | ||
}; | ||
|
||
const options: Intl.NumberFormatOptions = {}; | ||
if (formatNameOrArgs) { | ||
if (typeof formatNameOrArgs === 'string') { | ||
// Based on https://github.com/messageformat/messageformat/blob/main/packages/runtime/src/fmt/number.ts | ||
const [formatName, currency] = formatNameOrArgs.split(':') || []; | ||
|
||
if (formatNameOrArgs in allFormats) { | ||
Object.assign(options, (allFormats as any)[formatName]); | ||
} | ||
if (currency) { | ||
options.currency = currency; | ||
} | ||
} | ||
if (typeof formatNameOrArgs === 'object') { | ||
Object.assign(options, formatNameOrArgs.parsedOptions); | ||
} | ||
} | ||
|
||
// TODO: Caching? | ||
const format = new Intl.NumberFormat(locale, options); | ||
return format.format(value); | ||
} | ||
}; | ||
|
||
return formatters; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be removed from the parsed result? See also https://github.com/amannn/next-intl/pull/705/files#r1418720864
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use
compileToJson
instead it's the same ascompile
just withoutargs
args
are helpful to generate types or to optimize which formatters should be included to a bundle.e.g. if date is used you might want to add a date formatter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, thanks!