1717 */
1818
1919import { MessageLoader } from "./MessageLoader"
20- import { Language , Messages } from "./Messages"
20+ import { Language , Message , MessageKey , Messages , PluralKey } from "./Messages"
21+
22+ export type MessagesObject = { [ messageKey in MessageKey ] : string | { [ pluralKey : string ] : string } }
2123
2224/**
2325 * An implementation of `MessageLoader` that "loads" `Messages` from given Javascript objects.
2426 * This implemenation is especially usefull when loading messages from JSON files that are included
2527 * during Javascript assembly.
2628 */
2729export class ObjectMessageLoader implements MessageLoader {
28- constructor ( private readonly defaultMessages : Messages , private readonly messagesByLanguage : { [ key in Language ] : Messages } ) { }
30+ constructor ( private readonly defaultMessages : MessagesObject , private readonly messagesByLanguage : { [ key in Language ] : MessagesObject } ) { }
2931
3032 loadDefaultMessages ( ) : Promise < Messages > {
31- return Promise . resolve ( this . defaultMessages )
33+ return Promise . resolve ( this . transformMessagesObject ( this . defaultMessages ) )
3234 }
3335
3436 loadMessages ( language : Language ) : Promise < Messages | undefined > {
35- return Promise . resolve ( this . messagesByLanguage [ language ] )
37+ if ( typeof this . messagesByLanguage [ language ] === "undefined" ) {
38+ return Promise . resolve ( undefined )
39+ }
40+
41+ return Promise . resolve ( this . transformMessagesObject ( this . messagesByLanguage [ language ] ) )
42+ }
43+
44+ private transformMessagesObject ( o : MessagesObject ) : Messages {
45+ const m = new Map < MessageKey , Message > ( )
46+ Object . keys ( o ) . forEach ( key => m . set ( key , this . transformMessage ( o [ key ] ) ) )
47+ return m
48+ }
49+
50+ private transformMessage ( m : string | { [ pluralKey : string ] : string } ) : Message {
51+ if ( typeof m === "string" ) {
52+ return m
53+ }
54+
55+ const msg = new Map < PluralKey , string > ( )
56+
57+ Object . keys ( m ) . forEach ( k => msg . set ( k === "n" ? "n" : parseInt ( k ) , m [ k ] ) )
58+
59+ return msg
3660 }
3761}
3862
@@ -63,16 +87,79 @@ export class CascadingMessageLoader implements MessageLoader {
6387
6488 private async mergeMessages ( input : Array < Promise < Messages | undefined > > ) : Promise < Messages > {
6589 const loaded = await Promise . all ( input )
66- const msg = loaded [ 0 ] ?? { }
90+ const msg = loaded [ 0 ] ?? new Map < MessageKey , Message > ( )
91+
6792 loaded . slice ( 1 ) . forEach ( m => {
6893 if ( ! m ) {
6994 return
7095 }
71- Object . keys ( m ) . forEach ( k => {
72- msg [ k ] = m [ k ]
73- } )
96+ m . forEach ( ( val , key ) => msg . set ( key , val ) )
7497 } )
7598
7699 return msg
77100 }
101+ }
102+
103+ export type JsonMessages = { [ key : string ] : string | { [ pluralKey : string ] : string } }
104+
105+ /**
106+ * A strategy interface used for different loaders loading JSON representation
107+ * of a message.
108+ */
109+ export interface JsonSource {
110+ ( language ?: Language ) : Promise < string | undefined >
111+ }
112+
113+ export class JsonMessageLoaderError extends Error {
114+ constructor ( msg : string ) {
115+ super ( msg )
116+ }
117+ }
118+
119+ /**
120+ * A `MessageLoader` that loads messages from `JsonSource`s. The loader parses
121+ * the JSON and normalizes the plural messages by replacing string notated numbers,
122+ * i.e. `"1"` with their numeric keys.
123+ */
124+ export class JsonMessageLoader implements MessageLoader {
125+ constructor ( private readonly defaultsLoader : JsonSource , private readonly localizedLoader : JsonSource ) { }
126+
127+ loadDefaultMessages ( ) : Promise < Messages > {
128+ return this . parseJson ( this . defaultsLoader ( ) )
129+ }
130+
131+ loadMessages ( language : Language ) : Promise < Messages | undefined > {
132+ return this . parseJson ( this . localizedLoader ( language ) )
133+ }
134+
135+ private async parseJson ( input : Promise < string | undefined > ) : Promise < Messages | undefined > {
136+ const jsonString = await input
137+ if ( typeof jsonString === "undefined" ) {
138+ return jsonString
139+ }
140+
141+ const messages = new Map < MessageKey , Message > ( )
142+
143+ const parsed = JSON . parse ( jsonString ) as JsonMessages
144+ Object . keys ( parsed ) . forEach ( messageKey => {
145+ const msg = parsed [ messageKey ]
146+ if ( typeof msg === "string" ) {
147+ messages . set ( messageKey , msg )
148+ } else {
149+ const pluralMessage = new Map < PluralKey , string > ( )
150+ Object . keys ( parsed [ messageKey ] ) . forEach ( ( pluralKey : string ) => {
151+ if ( pluralKey . match ( / ^ [ 0 - 9 ] + $ / ) ) {
152+ pluralMessage . set ( parseInt ( pluralKey ) , msg [ pluralKey ] )
153+ } else if ( pluralKey === "n" ) {
154+ pluralMessage . set ( "n" , msg [ pluralKey ] )
155+ } else {
156+ throw new JsonMessageLoaderError ( `invalid plural key '${ pluralKey } ' for message key '${ messageKey } '` )
157+ }
158+ } )
159+ messages . set ( messageKey , pluralMessage )
160+ }
161+ } )
162+
163+ return messages
164+ }
78165}
0 commit comments